trackler 2.0.5.8 → 2.0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/common/exercises/sum-of-multiples/description.md +2 -2
- data/lib/trackler/version.rb +1 -1
- data/tracks/csharp/config.json +24 -0
- data/tracks/csharp/exercises/book-store/BookStoreTest.cs +95 -0
- data/tracks/csharp/exercises/book-store/Example.cs +70 -0
- data/tracks/csharp/exercises/connect/ConnectTest.cs +131 -0
- data/tracks/csharp/exercises/connect/Example.cs +121 -0
- data/tracks/csharp/exercises/exercises.csproj +1 -1
- data/tracks/csharp/exercises/markdown/Example.cs +108 -0
- data/tracks/csharp/exercises/markdown/HINTS.md +3 -0
- data/tracks/csharp/exercises/markdown/Markdown.cs +151 -0
- data/tracks/csharp/exercises/markdown/MarkdownTest.cs +76 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1f9e78737566c55c139024513a73f38029f2a0c
|
4
|
+
data.tar.gz: 853360aad86762df2a9279f3ad209e4497c739ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1517522938b3420f81dd931fb53458a99858c61e4673ec0413c120fc9aff827306e257fb5a316a21a2b771c12ae8a859ea943b53967ade8471031c9761e139e6
|
7
|
+
data.tar.gz: 527684182f9afe7b3e1e4a8b9fbc0a07e1a60dc0272d1188efb311678f065d4eb49118f5c059a87bb6d8a6daf0158a20e06dea9d45c69c746b01effb5541c3ed
|
@@ -3,5 +3,5 @@ multiples of either 3 or 5, we get 3, 5, 6 and 9, 10, 12, 15, and 18.
|
|
3
3
|
|
4
4
|
The sum of these multiples is 78.
|
5
5
|
|
6
|
-
Write a program that can find the sum of the multiples
|
7
|
-
numbers.
|
6
|
+
Write a program that, given a number, can find the sum of the multiples
|
7
|
+
of a given set of numbers, up to but not including that number.
|
data/lib/trackler/version.rb
CHANGED
data/tracks/csharp/config.json
CHANGED
@@ -560,6 +560,15 @@
|
|
560
560
|
"Transforming"
|
561
561
|
]
|
562
562
|
},
|
563
|
+
{
|
564
|
+
"slug": "markdown",
|
565
|
+
"difficulty": 5,
|
566
|
+
"topics": [
|
567
|
+
"Parsing",
|
568
|
+
"Transforming",
|
569
|
+
"Refactoring"
|
570
|
+
]
|
571
|
+
},
|
563
572
|
{
|
564
573
|
"slug": "run-length-encoding",
|
565
574
|
"difficulty": 5,
|
@@ -568,6 +577,13 @@
|
|
568
577
|
"Transforming"
|
569
578
|
]
|
570
579
|
},
|
580
|
+
{
|
581
|
+
"slug": "book-store",
|
582
|
+
"difficulty": 5,
|
583
|
+
"topics": [
|
584
|
+
"Recursion"
|
585
|
+
]
|
586
|
+
},
|
571
587
|
{
|
572
588
|
"slug": "tournament",
|
573
589
|
"difficulty": 6,
|
@@ -706,6 +722,14 @@
|
|
706
722
|
"Logic"
|
707
723
|
]
|
708
724
|
},
|
725
|
+
{
|
726
|
+
"slug": "connect",
|
727
|
+
"difficulty": 8,
|
728
|
+
"topics": [
|
729
|
+
"Parsing",
|
730
|
+
"Transforming"
|
731
|
+
]
|
732
|
+
},
|
709
733
|
{
|
710
734
|
"slug": "say",
|
711
735
|
"difficulty": 8,
|
@@ -0,0 +1,95 @@
|
|
1
|
+
using System.Collections.Generic;
|
2
|
+
using System.Linq;
|
3
|
+
using NUnit.Framework;
|
4
|
+
|
5
|
+
[TestFixture]
|
6
|
+
public class BookStoreTest
|
7
|
+
{
|
8
|
+
[Test]
|
9
|
+
public void Basket_with_single_book()
|
10
|
+
{
|
11
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1)), Is.EqualTo(8));
|
12
|
+
}
|
13
|
+
|
14
|
+
[Ignore("Remove to run test")]
|
15
|
+
[Test]
|
16
|
+
public void Basket_with_two_of_same_book()
|
17
|
+
{
|
18
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(2, 2)), Is.EqualTo(16));
|
19
|
+
}
|
20
|
+
|
21
|
+
[Ignore("Remove to run test")]
|
22
|
+
[Test]
|
23
|
+
public void Empty_basket()
|
24
|
+
{
|
25
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList()), Is.EqualTo(0));
|
26
|
+
}
|
27
|
+
|
28
|
+
[Ignore("Remove to run test")]
|
29
|
+
[Test]
|
30
|
+
public void Basket_with_two_different_books()
|
31
|
+
{
|
32
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2)), Is.EqualTo(15.2));
|
33
|
+
}
|
34
|
+
|
35
|
+
[Ignore("Remove to run test")]
|
36
|
+
[Test]
|
37
|
+
public void Basket_with_three_different_books()
|
38
|
+
{
|
39
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2, 3)), Is.EqualTo(21.6));
|
40
|
+
}
|
41
|
+
|
42
|
+
[Ignore("Remove to run test")]
|
43
|
+
[Test]
|
44
|
+
public void Basket_with_four_different_books()
|
45
|
+
{
|
46
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2, 3, 4)), Is.EqualTo(25.6));
|
47
|
+
}
|
48
|
+
|
49
|
+
[Ignore("Remove to run test")]
|
50
|
+
[Test]
|
51
|
+
public void Basket_with_five_different_books()
|
52
|
+
{
|
53
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 2, 3, 4, 5)), Is.EqualTo(30));
|
54
|
+
}
|
55
|
+
|
56
|
+
[Ignore("Remove to run test")]
|
57
|
+
[Test]
|
58
|
+
public void Basket_with_eight_books()
|
59
|
+
{
|
60
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 5)), Is.EqualTo(51.20));
|
61
|
+
}
|
62
|
+
|
63
|
+
[Ignore("Remove to run test")]
|
64
|
+
[Test]
|
65
|
+
public void Basket_with_nine_books()
|
66
|
+
{
|
67
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5)), Is.EqualTo(55.60));
|
68
|
+
}
|
69
|
+
|
70
|
+
[Ignore("Remove to run test")]
|
71
|
+
[Test]
|
72
|
+
public void Basket_with_ten_books()
|
73
|
+
{
|
74
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5)), Is.EqualTo(60));
|
75
|
+
}
|
76
|
+
|
77
|
+
[Ignore("Remove to run test")]
|
78
|
+
[Test]
|
79
|
+
public void Basket_with_eleven_books()
|
80
|
+
{
|
81
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1)), Is.EqualTo(68));
|
82
|
+
}
|
83
|
+
|
84
|
+
[Ignore("Remove to run test")]
|
85
|
+
[Test]
|
86
|
+
public void Basket_with_twelve_books()
|
87
|
+
{
|
88
|
+
Assert.That(BookStore.CalculateTotalCost(MakeList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2)), Is.EqualTo(75.20));
|
89
|
+
}
|
90
|
+
|
91
|
+
private static List<int> MakeList(params int[] values)
|
92
|
+
{
|
93
|
+
return values.ToList();
|
94
|
+
}
|
95
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Collections.Generic;
|
3
|
+
using System.Linq;
|
4
|
+
|
5
|
+
public class BookStore
|
6
|
+
{
|
7
|
+
public static double CalculateTotalCost(List<int> books)
|
8
|
+
{
|
9
|
+
return CalculateTotalCost(books, 0);
|
10
|
+
}
|
11
|
+
|
12
|
+
private static double CalculateTotalCost(List<int> books, double priceSoFar)
|
13
|
+
{
|
14
|
+
if (books.Count == 0)
|
15
|
+
{
|
16
|
+
return priceSoFar;
|
17
|
+
}
|
18
|
+
|
19
|
+
var groups = books
|
20
|
+
.GroupBy(b => b)
|
21
|
+
.Select(g => g.Key)
|
22
|
+
.ToList();
|
23
|
+
|
24
|
+
var minPrice = double.MaxValue;
|
25
|
+
|
26
|
+
for (int i = groups.Count; i >= 1; i--)
|
27
|
+
{
|
28
|
+
var itemsToRemove = groups.Take(i).ToList();
|
29
|
+
var remaining = books.ToList();
|
30
|
+
|
31
|
+
foreach (var item in itemsToRemove)
|
32
|
+
{
|
33
|
+
remaining.Remove(item);
|
34
|
+
}
|
35
|
+
|
36
|
+
var price = CalculateTotalCost(remaining.ToList(), priceSoFar + CostPerGroup(i));
|
37
|
+
minPrice = Math.Min(minPrice, price);
|
38
|
+
}
|
39
|
+
|
40
|
+
return minPrice;
|
41
|
+
}
|
42
|
+
|
43
|
+
private static double CostPerGroup(int groupSize)
|
44
|
+
{
|
45
|
+
double discountPercentage;
|
46
|
+
|
47
|
+
switch (groupSize)
|
48
|
+
{
|
49
|
+
case 1:
|
50
|
+
discountPercentage = 0;
|
51
|
+
break;
|
52
|
+
case 2:
|
53
|
+
discountPercentage = 5;
|
54
|
+
break;
|
55
|
+
case 3:
|
56
|
+
discountPercentage = 10;
|
57
|
+
break;
|
58
|
+
case 4:
|
59
|
+
discountPercentage = 20;
|
60
|
+
break;
|
61
|
+
case 5:
|
62
|
+
discountPercentage = 25;
|
63
|
+
break;
|
64
|
+
default:
|
65
|
+
throw new InvalidOperationException($"Invalid group size: {groupSize}");
|
66
|
+
}
|
67
|
+
|
68
|
+
return 8 * groupSize * (100 - discountPercentage) / 100;
|
69
|
+
}
|
70
|
+
}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
using NUnit.Framework;
|
2
|
+
using System.Linq;
|
3
|
+
|
4
|
+
public class ConnectTest
|
5
|
+
{
|
6
|
+
private static string MakeBoard(string[] board)
|
7
|
+
{
|
8
|
+
return string.Join("\n", board.Select(x => x.Replace(" ", "")));
|
9
|
+
}
|
10
|
+
|
11
|
+
[Test]
|
12
|
+
public void Empty_board_has_no_winner()
|
13
|
+
{
|
14
|
+
var lines = new[]
|
15
|
+
{
|
16
|
+
". . . . . ",
|
17
|
+
" . . . . . ",
|
18
|
+
" . . . . . ",
|
19
|
+
" . . . . . ",
|
20
|
+
" . . . . ."
|
21
|
+
};
|
22
|
+
var board = new Connect(MakeBoard(lines));
|
23
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.None));
|
24
|
+
}
|
25
|
+
|
26
|
+
[Ignore("Remove to run test")]
|
27
|
+
[Test]
|
28
|
+
public void One_by_one_board_with_black_stone()
|
29
|
+
{
|
30
|
+
var lines = new[] { "X" };
|
31
|
+
var board = new Connect(MakeBoard(lines));
|
32
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
33
|
+
}
|
34
|
+
|
35
|
+
[Ignore("Remove to run test")]
|
36
|
+
[Test]
|
37
|
+
public void One_by_one_board_with_white_stone()
|
38
|
+
{
|
39
|
+
var lines = new[] { "O" };
|
40
|
+
var board = new Connect(MakeBoard(lines));
|
41
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.White));
|
42
|
+
}
|
43
|
+
|
44
|
+
[Ignore("Remove to run test")]
|
45
|
+
[Test]
|
46
|
+
public void Convoluted_path()
|
47
|
+
{
|
48
|
+
var lines = new[]
|
49
|
+
{
|
50
|
+
". X X . . ",
|
51
|
+
" X . X . X ",
|
52
|
+
" . X . X . ",
|
53
|
+
" . X X . . ",
|
54
|
+
" O O O O O"
|
55
|
+
};
|
56
|
+
var board = new Connect(MakeBoard(lines));
|
57
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
58
|
+
}
|
59
|
+
|
60
|
+
[Ignore("Remove to run test")]
|
61
|
+
[Test]
|
62
|
+
public void Rectangle_black_wins()
|
63
|
+
{
|
64
|
+
var lines = new[]
|
65
|
+
{
|
66
|
+
". O . . ",
|
67
|
+
" O X X X ",
|
68
|
+
" O X O . ",
|
69
|
+
" X X O X ",
|
70
|
+
" . O X ."
|
71
|
+
};
|
72
|
+
var board = new Connect(MakeBoard(lines));
|
73
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
74
|
+
}
|
75
|
+
|
76
|
+
[Ignore("Remove to run test")]
|
77
|
+
[Test]
|
78
|
+
public void Rectangle_white_wins()
|
79
|
+
{
|
80
|
+
var lines = new[]
|
81
|
+
{
|
82
|
+
". O . . ",
|
83
|
+
" O X X X ",
|
84
|
+
" O O O . ",
|
85
|
+
" X X O X ",
|
86
|
+
" . O X ."
|
87
|
+
};
|
88
|
+
var board = new Connect(MakeBoard(lines));
|
89
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.White));
|
90
|
+
}
|
91
|
+
|
92
|
+
[Ignore("Remove to run test")]
|
93
|
+
[Test]
|
94
|
+
public void Spiral_black_wins()
|
95
|
+
{
|
96
|
+
var lines = new[]
|
97
|
+
{
|
98
|
+
"OXXXXXXXX",
|
99
|
+
"OXOOOOOOO",
|
100
|
+
"OXOXXXXXO",
|
101
|
+
"OXOXOOOXO",
|
102
|
+
"OXOXXXOXO",
|
103
|
+
"OXOOOXOXO",
|
104
|
+
"OXXXXXOXO",
|
105
|
+
"OOOOOOOXO",
|
106
|
+
"XXXXXXXXO"
|
107
|
+
};
|
108
|
+
var board = new Connect(MakeBoard(lines));
|
109
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.Black));
|
110
|
+
}
|
111
|
+
|
112
|
+
[Ignore("Remove to run test")]
|
113
|
+
[Test]
|
114
|
+
public void Spiral_nobody_wins()
|
115
|
+
{
|
116
|
+
var lines = new[]
|
117
|
+
{
|
118
|
+
"OXXXXXXXX",
|
119
|
+
"OXOOOOOOO",
|
120
|
+
"OXOXXXXXO",
|
121
|
+
"OXOXOOOXO",
|
122
|
+
"OXOX.XOXO",
|
123
|
+
"OXOOOXOXO",
|
124
|
+
"OXXXXXOXO",
|
125
|
+
"OOOOOOOXO",
|
126
|
+
"XXXXXXXXO"
|
127
|
+
};
|
128
|
+
var board = new Connect(MakeBoard(lines));
|
129
|
+
Assert.That(board.Result(), Is.EqualTo(Connect.Winner.None));
|
130
|
+
}
|
131
|
+
}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Collections.Generic;
|
3
|
+
using System.Drawing;
|
4
|
+
using System.Linq;
|
5
|
+
|
6
|
+
public class Connect
|
7
|
+
{
|
8
|
+
public enum Winner
|
9
|
+
{
|
10
|
+
White,
|
11
|
+
Black,
|
12
|
+
None
|
13
|
+
}
|
14
|
+
|
15
|
+
public enum Cell
|
16
|
+
{
|
17
|
+
Empty,
|
18
|
+
White,
|
19
|
+
Black
|
20
|
+
}
|
21
|
+
|
22
|
+
private readonly Cell[][] board;
|
23
|
+
|
24
|
+
public Connect(string input)
|
25
|
+
{
|
26
|
+
board = ParseBoard(input);
|
27
|
+
}
|
28
|
+
|
29
|
+
private static Cell CharToCell(char c)
|
30
|
+
{
|
31
|
+
switch (c)
|
32
|
+
{
|
33
|
+
case 'O': return Cell.White;
|
34
|
+
case 'X': return Cell.Black;
|
35
|
+
default: return Cell.Empty;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
private static Cell[][] ParseBoard(string input)
|
40
|
+
{
|
41
|
+
var split = input.Split('\n');
|
42
|
+
var rows = split.Length;
|
43
|
+
var cols = split[0].Length;
|
44
|
+
|
45
|
+
return split.Select(row => row.Select(CharToCell).ToArray()).ToArray();
|
46
|
+
}
|
47
|
+
|
48
|
+
private int Cols => board[0].Length;
|
49
|
+
private int Rows => board.Length;
|
50
|
+
|
51
|
+
private bool IsValidCoordinate(Point coordinate) =>
|
52
|
+
coordinate.Y >= 0 && coordinate.Y < Rows &&
|
53
|
+
coordinate.X >= 0 && coordinate.X < Cols;
|
54
|
+
|
55
|
+
private bool CellAtCoordinateEquals(Cell cell, Point coordinate) => board[coordinate.Y][coordinate.X] == cell;
|
56
|
+
|
57
|
+
private HashSet<Point> Adjacent(Cell cell, Point coordinate)
|
58
|
+
{
|
59
|
+
var row = coordinate.Y;
|
60
|
+
var col = coordinate.X;
|
61
|
+
|
62
|
+
var coords = new[]
|
63
|
+
{
|
64
|
+
new Point(col + 1, row - 1),
|
65
|
+
new Point(col, row - 1),
|
66
|
+
new Point(col - 1, row ),
|
67
|
+
new Point(col + 1, row ),
|
68
|
+
new Point(col - 1, row + 1),
|
69
|
+
new Point(col, row + 1)
|
70
|
+
};
|
71
|
+
|
72
|
+
return new HashSet<Point>(coords.Where(coord => IsValidCoordinate(coord) && CellAtCoordinateEquals(cell, coord)));
|
73
|
+
}
|
74
|
+
|
75
|
+
private bool ValidPath(Cell cell, Func<Cell[][], Point, bool> stop, HashSet<Point> processed, Point coordinate)
|
76
|
+
{
|
77
|
+
if (stop(board, coordinate))
|
78
|
+
return true;
|
79
|
+
|
80
|
+
var next = Adjacent(cell, coordinate);
|
81
|
+
next.ExceptWith(processed);
|
82
|
+
|
83
|
+
if (!next.Any())
|
84
|
+
return false;
|
85
|
+
|
86
|
+
return next.Any(nextCoord => {
|
87
|
+
var updatedProcessed = new HashSet<Point>(processed);
|
88
|
+
updatedProcessed.Add(nextCoord);
|
89
|
+
|
90
|
+
return ValidPath(cell, stop, updatedProcessed, nextCoord);
|
91
|
+
});
|
92
|
+
}
|
93
|
+
|
94
|
+
private bool IsWhiteStop(Cell[][] board, Point coordinate) => coordinate.Y == Rows - 1;
|
95
|
+
private bool IsBlackStop(Cell[][] board, Point coordinate) => coordinate.X == Cols - 1;
|
96
|
+
|
97
|
+
private HashSet<Point> WhiteStart() =>
|
98
|
+
new HashSet<Point>(Enumerable.Range(0, Cols).Select(col => new Point(col, 0)).Where(coord => CellAtCoordinateEquals(Cell.White, coord)));
|
99
|
+
|
100
|
+
private HashSet<Point> BlackStart() =>
|
101
|
+
new HashSet<Point>(Enumerable.Range(0, Rows).Select(row => new Point(0, row)).Where(coord => CellAtCoordinateEquals(Cell.Black, coord)));
|
102
|
+
|
103
|
+
private bool ColorWins(Cell cell, Func<Cell[][], Point, bool> stop, Func<HashSet<Point>> start)
|
104
|
+
{
|
105
|
+
return start().Any(coordinate => ValidPath(cell, stop, new HashSet<Point>(), coordinate));
|
106
|
+
}
|
107
|
+
|
108
|
+
private bool WhiteWins() => ColorWins(Cell.White, IsWhiteStop, WhiteStart);
|
109
|
+
private bool BlackWins() => ColorWins(Cell.Black, IsBlackStop, BlackStart);
|
110
|
+
|
111
|
+
public Winner Result()
|
112
|
+
{
|
113
|
+
if (WhiteWins())
|
114
|
+
return Winner.White;
|
115
|
+
|
116
|
+
if (BlackWins())
|
117
|
+
return Winner.Black;
|
118
|
+
|
119
|
+
return Winner.None;
|
120
|
+
}
|
121
|
+
}
|
@@ -38,7 +38,7 @@
|
|
38
38
|
<Reference Include="Microsoft.CSharp" />
|
39
39
|
</ItemGroup>
|
40
40
|
<ItemGroup>
|
41
|
-
<Compile Include="**\*.cs" Exclude="sgf-parsing\SgfParsing.cs" />
|
41
|
+
<Compile Include="**\*.cs" Exclude="sgf-parsing\SgfParsing.cs;markdown\Markdown.cs" />
|
42
42
|
</ItemGroup>
|
43
43
|
<ItemGroup>
|
44
44
|
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
@@ -0,0 +1,108 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Linq;
|
3
|
+
using System.Text.RegularExpressions;
|
4
|
+
|
5
|
+
public static class Markdown
|
6
|
+
{
|
7
|
+
private static string OpeningTag(string tag) => $"<{tag}>";
|
8
|
+
private static string ClosingTag(string tag) => $"</{tag}>";
|
9
|
+
private static string WrapInTag(this string text, string tag) => $"{OpeningTag(tag)}{text}{ClosingTag(tag)}";
|
10
|
+
private static bool StartsWithTag(this string text, string tag) => text.StartsWith(OpeningTag(tag));
|
11
|
+
|
12
|
+
private const string HeaderMarkdown = "#";
|
13
|
+
private const string BoldMarkdown = "__";
|
14
|
+
private const string ItalicMarkdown = "_";
|
15
|
+
private const string ListItemMarkdown = "*";
|
16
|
+
|
17
|
+
private const string BoldTag = "em";
|
18
|
+
private const string ItalicTag = "i";
|
19
|
+
private const string ParagraphTag = "p";
|
20
|
+
private const string ListTag = "ul";
|
21
|
+
private const string ListItemTag = "li";
|
22
|
+
|
23
|
+
private static string ParseDelimited(this string markdown, string delimiter, string tag)
|
24
|
+
{
|
25
|
+
var pattern = $"{delimiter}(.+){delimiter}";
|
26
|
+
var replacement = "$1".WrapInTag(tag);
|
27
|
+
return Regex.Replace(markdown, pattern, replacement);
|
28
|
+
}
|
29
|
+
|
30
|
+
private static string ParseBold(this string markdown) => markdown.ParseDelimited(BoldMarkdown, BoldTag);
|
31
|
+
private static string ParseItalic(this string markdown) => markdown.ParseDelimited(ItalicMarkdown, ItalicTag);
|
32
|
+
|
33
|
+
private static string ParseText(this string markdown, bool list)
|
34
|
+
{
|
35
|
+
var textHtml = markdown
|
36
|
+
.ParseBold()
|
37
|
+
.ParseItalic();
|
38
|
+
|
39
|
+
return list ? textHtml : textHtml.WrapInTag(ParagraphTag);
|
40
|
+
}
|
41
|
+
|
42
|
+
private static Tuple<bool, string> ParseHeader(this string markdown, bool list)
|
43
|
+
{
|
44
|
+
var headerNumber =
|
45
|
+
markdown
|
46
|
+
.TakeWhile(c => c == HeaderMarkdown[0])
|
47
|
+
.Count();
|
48
|
+
|
49
|
+
if (headerNumber == 0)
|
50
|
+
return null;
|
51
|
+
|
52
|
+
var headerTag = $"h{headerNumber}";
|
53
|
+
var headerHtml = markdown.Substring(headerNumber + 1).WrapInTag(headerTag);
|
54
|
+
var html = list ? ClosingTag(ListTag) + headerHtml : headerHtml;
|
55
|
+
|
56
|
+
return Tuple.Create(false, html);
|
57
|
+
}
|
58
|
+
|
59
|
+
private static Tuple<bool, string> ParseLineItem(this string markdown, bool list)
|
60
|
+
{
|
61
|
+
if (!markdown.StartsWith(ListItemMarkdown))
|
62
|
+
return null;
|
63
|
+
|
64
|
+
var innerHtml =
|
65
|
+
markdown
|
66
|
+
.Substring(2)
|
67
|
+
.ParseText(true)
|
68
|
+
.WrapInTag(ListItemTag);
|
69
|
+
|
70
|
+
var html = list ? innerHtml : OpeningTag(ListTag) + innerHtml;
|
71
|
+
return Tuple.Create(true, html);
|
72
|
+
}
|
73
|
+
|
74
|
+
private static Tuple<bool, string> ParseParagraph(this string markdown, bool list)
|
75
|
+
{
|
76
|
+
if (list)
|
77
|
+
return Tuple.Create(false, ClosingTag(ListTag) + markdown.ParseText(list));
|
78
|
+
|
79
|
+
return Tuple.Create(false, markdown.ParseText(list));
|
80
|
+
}
|
81
|
+
|
82
|
+
private static Tuple<bool, string> ParseLine(Tuple<bool, string> accumulator, string markdown)
|
83
|
+
{
|
84
|
+
var list = accumulator.Item1;
|
85
|
+
var html = accumulator.Item2;
|
86
|
+
|
87
|
+
var result =
|
88
|
+
markdown.ParseHeader(list) ??
|
89
|
+
markdown.ParseLineItem(list) ??
|
90
|
+
markdown.ParseParagraph(list);
|
91
|
+
|
92
|
+
if (result == null)
|
93
|
+
throw new ArgumentException("Invalid markdown");
|
94
|
+
|
95
|
+
return Tuple.Create(result.Item1, html + result.Item2);
|
96
|
+
}
|
97
|
+
|
98
|
+
public static string Parse(string markdown)
|
99
|
+
{
|
100
|
+
var lines = markdown.Split('\n');
|
101
|
+
var result = lines.Aggregate(Tuple.Create(false, ""), ParseLine);
|
102
|
+
|
103
|
+
var list = result.Item1;
|
104
|
+
var html = result.Item2;
|
105
|
+
|
106
|
+
return list ? html + ClosingTag(ListTag) : html;
|
107
|
+
}
|
108
|
+
}
|
@@ -0,0 +1,151 @@
|
|
1
|
+
using System;
|
2
|
+
using System.Text.RegularExpressions;
|
3
|
+
|
4
|
+
public static class Markdown
|
5
|
+
{
|
6
|
+
private static string Wrap(string text, string tag) => "<" + tag + ">" + text + "</" + tag + ">";
|
7
|
+
|
8
|
+
private static bool IsTag(string text, string tag) => text.StartsWith("<" + tag + ">");
|
9
|
+
|
10
|
+
private static string Parse(string markdown, string delimiter, string tag)
|
11
|
+
{
|
12
|
+
var pattern = delimiter + "(.+)" + delimiter;
|
13
|
+
var replacement = "<" + tag + ">$1</" + tag + ">";
|
14
|
+
return Regex.Replace(markdown, pattern, replacement);
|
15
|
+
}
|
16
|
+
|
17
|
+
private static string Parse__(string markdown) => Parse(markdown, "__", "em");
|
18
|
+
|
19
|
+
private static string Parse_(string markdown) => Parse(markdown, "_", "i");
|
20
|
+
|
21
|
+
private static string ParseText(string markdown, bool list)
|
22
|
+
{
|
23
|
+
var parsedText = Parse_(Parse__((markdown)));
|
24
|
+
|
25
|
+
if (list)
|
26
|
+
{
|
27
|
+
return parsedText;
|
28
|
+
}
|
29
|
+
else
|
30
|
+
{
|
31
|
+
return Wrap(parsedText, "p");
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
private static string ParseHeader(string markdown, bool list, out bool inListAfter)
|
36
|
+
{
|
37
|
+
var count = 0;
|
38
|
+
|
39
|
+
for (int i = 0; i < markdown.Length; i++)
|
40
|
+
{
|
41
|
+
if (markdown[i] == '#')
|
42
|
+
{
|
43
|
+
count += 1;
|
44
|
+
}
|
45
|
+
else
|
46
|
+
{
|
47
|
+
break;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
if (count == 0)
|
52
|
+
{
|
53
|
+
inListAfter = list;
|
54
|
+
return null;
|
55
|
+
}
|
56
|
+
|
57
|
+
var headerTag = "h" + count;
|
58
|
+
var headerHtml = Wrap(markdown.Substring(count + 1), headerTag);
|
59
|
+
|
60
|
+
if (list)
|
61
|
+
{
|
62
|
+
inListAfter = false;
|
63
|
+
return "</ul>" + headerHtml;
|
64
|
+
}
|
65
|
+
else
|
66
|
+
{
|
67
|
+
inListAfter = false;
|
68
|
+
return headerHtml;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
private static string ParseLineItem(string markdown, bool list, out bool inListAfter)
|
73
|
+
{
|
74
|
+
if (markdown.StartsWith("*"))
|
75
|
+
{
|
76
|
+
var innerHtml = Wrap(ParseText(markdown.Substring(2), true), "li");
|
77
|
+
|
78
|
+
if (list)
|
79
|
+
{
|
80
|
+
inListAfter = true;
|
81
|
+
return innerHtml;
|
82
|
+
}
|
83
|
+
else
|
84
|
+
{
|
85
|
+
inListAfter = true;
|
86
|
+
return "<ul>" + innerHtml;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
inListAfter = list;
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
|
94
|
+
private static string ParseParagraph(string markdown, bool list, out bool inListAfter)
|
95
|
+
{
|
96
|
+
if (!list)
|
97
|
+
{
|
98
|
+
inListAfter = false;
|
99
|
+
return ParseText(markdown, list);
|
100
|
+
}
|
101
|
+
else
|
102
|
+
{
|
103
|
+
inListAfter = false;
|
104
|
+
return "</ul>" + ParseText(markdown, list);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
private static string ParseLine(string markdown, bool list, out bool inListAfter)
|
109
|
+
{
|
110
|
+
var result = ParseHeader(markdown, list, out inListAfter);
|
111
|
+
|
112
|
+
if (result == null)
|
113
|
+
{
|
114
|
+
result = ParseLineItem(markdown, list, out inListAfter);
|
115
|
+
}
|
116
|
+
|
117
|
+
if (result == null)
|
118
|
+
{
|
119
|
+
result = ParseParagraph(markdown, list, out inListAfter);
|
120
|
+
}
|
121
|
+
|
122
|
+
if (result == null)
|
123
|
+
{
|
124
|
+
throw new ArgumentException("Invalid markdown");
|
125
|
+
}
|
126
|
+
|
127
|
+
return result;
|
128
|
+
}
|
129
|
+
|
130
|
+
public static string Parse(string markdown)
|
131
|
+
{
|
132
|
+
var lines = markdown.Split('\n');
|
133
|
+
var result = "";
|
134
|
+
var list = false;
|
135
|
+
|
136
|
+
for (int i = 0; i < lines.Length; i++)
|
137
|
+
{
|
138
|
+
var lineResult = ParseLine(lines[i], list, out list);
|
139
|
+
result += lineResult;
|
140
|
+
}
|
141
|
+
|
142
|
+
if (list)
|
143
|
+
{
|
144
|
+
return result + "</ul>";
|
145
|
+
}
|
146
|
+
else
|
147
|
+
{
|
148
|
+
return result;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
using NUnit.Framework;
|
2
|
+
|
3
|
+
public class MarkdownTest
|
4
|
+
{
|
5
|
+
[Test]
|
6
|
+
public void Parses_normal_text_as_a_paragraph()
|
7
|
+
{
|
8
|
+
var input = "This will be a paragraph";
|
9
|
+
var expected = "<p>This will be a paragraph</p>";
|
10
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
11
|
+
}
|
12
|
+
|
13
|
+
[Test]
|
14
|
+
public void Parsing_italics()
|
15
|
+
{
|
16
|
+
var input = "_This will be italic_";
|
17
|
+
var expected = "<p><i>This will be italic</i></p>";
|
18
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
19
|
+
}
|
20
|
+
|
21
|
+
[Test]
|
22
|
+
public void Parsing_bold_text()
|
23
|
+
{
|
24
|
+
var input = "__This will be bold__";
|
25
|
+
var expected = "<p><em>This will be bold</em></p>";
|
26
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
27
|
+
}
|
28
|
+
|
29
|
+
[Test]
|
30
|
+
public void Mixed_normal_italics_and_bold_text()
|
31
|
+
{
|
32
|
+
var input = "This will _be_ __mixed__";
|
33
|
+
var expected = "<p>This will <i>be</i> <em>mixed</em></p>";
|
34
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
35
|
+
}
|
36
|
+
|
37
|
+
[Test]
|
38
|
+
public void With_h1_header_level()
|
39
|
+
{
|
40
|
+
var input = "# This will be an h1";
|
41
|
+
var expected = "<h1>This will be an h1</h1>";
|
42
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
43
|
+
}
|
44
|
+
|
45
|
+
[Test]
|
46
|
+
public void With_h2_header_level()
|
47
|
+
{
|
48
|
+
var input = "## This will be an h2";
|
49
|
+
var expected = "<h2>This will be an h2</h2>";
|
50
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
51
|
+
}
|
52
|
+
|
53
|
+
[Test]
|
54
|
+
public void With_h6_header_level()
|
55
|
+
{
|
56
|
+
var input = "###### This will be an h6";
|
57
|
+
var expected = "<h6>This will be an h6</h6>";
|
58
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
59
|
+
}
|
60
|
+
|
61
|
+
[Test]
|
62
|
+
public void Unordered_lists()
|
63
|
+
{
|
64
|
+
var input = "* Item 1\n* Item 2";
|
65
|
+
var expected = "<ul><li>Item 1</li><li>Item 2</li></ul>";
|
66
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
67
|
+
}
|
68
|
+
|
69
|
+
[Test]
|
70
|
+
public void With_a_little_bit_of_everything()
|
71
|
+
{
|
72
|
+
var input = "# Header!\n* __Bold Item__\n* _Italic Item_";
|
73
|
+
var expected = "<h1>Header!</h1><ul><li><em>Bold Item</em></li><li><i>Italic Item</i></li></ul>";
|
74
|
+
Assert.That(Markdown.Parse(input), Is.EqualTo(expected));
|
75
|
+
}
|
76
|
+
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trackler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.5.
|
4
|
+
version: 2.0.5.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katrina Owen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-12-
|
11
|
+
date: 2016-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -1298,6 +1298,8 @@ files:
|
|
1298
1298
|
- tracks/csharp/exercises/binary-search/Example.cs
|
1299
1299
|
- tracks/csharp/exercises/bob/BobTest.cs
|
1300
1300
|
- tracks/csharp/exercises/bob/Example.cs
|
1301
|
+
- tracks/csharp/exercises/book-store/BookStoreTest.cs
|
1302
|
+
- tracks/csharp/exercises/book-store/Example.cs
|
1301
1303
|
- tracks/csharp/exercises/bowling/BowlingTest.cs
|
1302
1304
|
- tracks/csharp/exercises/bowling/Example.cs
|
1303
1305
|
- tracks/csharp/exercises/bracket-push/BracketPushTest.cs
|
@@ -1308,6 +1310,8 @@ files:
|
|
1308
1310
|
- tracks/csharp/exercises/circular-buffer/Example.cs
|
1309
1311
|
- tracks/csharp/exercises/clock/ClockTest.cs
|
1310
1312
|
- tracks/csharp/exercises/clock/Example.cs
|
1313
|
+
- tracks/csharp/exercises/connect/ConnectTest.cs
|
1314
|
+
- tracks/csharp/exercises/connect/Example.cs
|
1311
1315
|
- tracks/csharp/exercises/crypto-square/CryptoSquareTest.cs
|
1312
1316
|
- tracks/csharp/exercises/crypto-square/Example.cs
|
1313
1317
|
- tracks/csharp/exercises/custom-set/CustomSetTest.cs
|
@@ -1356,6 +1360,10 @@ files:
|
|
1356
1360
|
- tracks/csharp/exercises/list-ops/ListOpsTest.cs
|
1357
1361
|
- tracks/csharp/exercises/luhn/Example.cs
|
1358
1362
|
- tracks/csharp/exercises/luhn/LuhnTest.cs
|
1363
|
+
- tracks/csharp/exercises/markdown/Example.cs
|
1364
|
+
- tracks/csharp/exercises/markdown/HINTS.md
|
1365
|
+
- tracks/csharp/exercises/markdown/Markdown.cs
|
1366
|
+
- tracks/csharp/exercises/markdown/MarkdownTest.cs
|
1359
1367
|
- tracks/csharp/exercises/matrix/Example.cs
|
1360
1368
|
- tracks/csharp/exercises/matrix/MatrixTest.cs
|
1361
1369
|
- tracks/csharp/exercises/meetup/Example.cs
|