trackler 2.2.1.139 → 2.2.1.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/tracks/csharp/exercises/matrix/Example.cs +1 -1
- data/tracks/csharp/exercises/matrix/Matrix.cs +2 -2
- data/tracks/csharp/exercises/matrix/MatrixTest.cs +41 -48
- data/tracks/csharp/exercises/twelve-days/Example.cs +17 -22
- data/tracks/csharp/exercises/twelve-days/README.md +0 -11
- data/tracks/csharp/exercises/twelve-days/TwelveDays.cs +3 -8
- data/tracks/csharp/exercises/twelve-days/TwelveDaysTest.cs +69 -56
- data/tracks/csharp/generators/Exercises/Matrix.cs +18 -0
- data/tracks/csharp/generators/Exercises/TwelveDays.cs +28 -0
- data/tracks/elm/docs/TESTS.md +3 -4
- data/tracks/erlang/config.json +8 -0
- data/tracks/erlang/exercises/pascals-triangle/README.md +64 -0
- data/tracks/erlang/exercises/pascals-triangle/rebar.config +30 -0
- data/tracks/erlang/exercises/pascals-triangle/src/example.erl +30 -0
- data/tracks/erlang/exercises/pascals-triangle/src/pascals_triangle.app.src +9 -0
- data/tracks/erlang/exercises/pascals-triangle/src/pascals_triangle.erl +8 -0
- data/tracks/erlang/exercises/pascals-triangle/test/pascals_triangle_tests.erl +51 -0
- data/tracks/fsharp/exercises/diffie-hellman/DiffieHellmanTest.fs +30 -50
- data/tracks/fsharp/exercises/yacht/YachtTest.fs +5 -1
- data/tracks/fsharp/generators/Generators.fs +53 -0
- data/tracks/go/config.json +12 -0
- data/tracks/go/exercises/markdown/.meta/gen.go +59 -0
- data/tracks/go/exercises/markdown/README.md +35 -0
- data/tracks/go/exercises/markdown/cases_test.go +59 -0
- data/tracks/go/exercises/markdown/example.go +70 -0
- data/tracks/go/exercises/markdown/markdown.go +66 -0
- data/tracks/go/exercises/markdown/markdown_test.go +21 -0
- data/tracks/python/exercises/crypto-square/README.md +23 -19
- data/tracks/python/exercises/yacht/yacht_test.py +4 -1
- data/tracks/swift/exercises/phone-number/Tests/PhoneNumberTests/PhoneNumberTests.swift +14 -14
- metadata +16 -2
@@ -0,0 +1,30 @@
|
|
1
|
+
%% Erlang compiler options
|
2
|
+
{erl_opts, [debug_info]}.
|
3
|
+
|
4
|
+
{deps, [{erl_exercism, "0.1.1"}]}.
|
5
|
+
|
6
|
+
{dialyzer, [
|
7
|
+
{warnings, [underspecs, no_return]},
|
8
|
+
{get_warnings, true},
|
9
|
+
{plt_apps, top_level_deps}, % top_level_deps | all_deps
|
10
|
+
{plt_extra_apps, []},
|
11
|
+
{plt_location, local}, % local | "/my/file/name"
|
12
|
+
{plt_prefix, "rebar3"},
|
13
|
+
{base_plt_apps, [stdlib, kernel, crypto]},
|
14
|
+
{base_plt_location, global}, % global | "/my/file/name"
|
15
|
+
{base_plt_prefix, "rebar3"}
|
16
|
+
]}.
|
17
|
+
|
18
|
+
%% eunit:test(Tests)
|
19
|
+
{eunit_tests, []}.
|
20
|
+
%% Options for eunit:test(Tests, Opts)
|
21
|
+
{eunit_opts, [verbose]}.
|
22
|
+
|
23
|
+
%% == xref ==
|
24
|
+
|
25
|
+
{xref_warnings, true}.
|
26
|
+
|
27
|
+
%% xref checks to run
|
28
|
+
{xref_checks, [undefined_function_calls, undefined_functions,
|
29
|
+
locals_not_used, exports_not_used,
|
30
|
+
deprecated_function_calls, deprecated_functions]}.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
-module(example).
|
2
|
+
|
3
|
+
-export([gen_pascals_triangle/1, test_version/0]).
|
4
|
+
|
5
|
+
gen_pascals_triangle(N) when N < 0 -> -1;
|
6
|
+
gen_pascals_triangle(0) -> [];
|
7
|
+
gen_pascals_triangle(N) ->
|
8
|
+
gen_pascals_triangle_helper(1, N, []).
|
9
|
+
|
10
|
+
gen_pascals_triangle_helper(Count, N, CurrentResult) ->
|
11
|
+
case Count > N of
|
12
|
+
true -> CurrentResult;
|
13
|
+
false ->
|
14
|
+
gen_pascals_triangle_helper(
|
15
|
+
Count + 1,
|
16
|
+
N,
|
17
|
+
CurrentResult ++ gen_next(CurrentResult))
|
18
|
+
end.
|
19
|
+
|
20
|
+
gen_next([]) -> [[1]];
|
21
|
+
gen_next(CurrentResult) ->
|
22
|
+
LastRowDropLast = lists:droplast(lists:last(CurrentResult)),
|
23
|
+
ReverseLastRowDropLast = lists:reverse(LastRowDropLast),
|
24
|
+
ZipList = lists:zipwith(
|
25
|
+
fun(X, Y) -> X + Y end,
|
26
|
+
LastRowDropLast,
|
27
|
+
ReverseLastRowDropLast),
|
28
|
+
[[1] ++ ZipList ++ [1]].
|
29
|
+
|
30
|
+
test_version() -> 1.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
-module(pascals_triangle_tests).
|
2
|
+
|
3
|
+
-include_lib("erl_exercism/include/exercism.hrl").
|
4
|
+
-include_lib("eunit/include/eunit.hrl").
|
5
|
+
|
6
|
+
|
7
|
+
% test cases adapted from `x-common//canonical-data.json` @ version: 1.3.0
|
8
|
+
|
9
|
+
zero_row_test() ->
|
10
|
+
?assertEqual([], pascals_triangle:gen_pascals_triangle(0)).
|
11
|
+
|
12
|
+
one_row_test() ->
|
13
|
+
?assertEqual([[1]], pascals_triangle:gen_pascals_triangle(1)).
|
14
|
+
|
15
|
+
two_rows_test() ->
|
16
|
+
?assertEqual([[1], [1, 1]], pascals_triangle:gen_pascals_triangle(2)).
|
17
|
+
|
18
|
+
three_rows_test() ->
|
19
|
+
?assertEqual([[1], [1, 1], [1, 2, 1]], pascals_triangle:gen_pascals_triangle(3)).
|
20
|
+
|
21
|
+
four_rows_test() ->
|
22
|
+
?assertEqual(
|
23
|
+
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]],
|
24
|
+
pascals_triangle:gen_pascals_triangle(4)).
|
25
|
+
|
26
|
+
five_rows_test() ->
|
27
|
+
?assertEqual(
|
28
|
+
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]],
|
29
|
+
pascals_triangle:gen_pascals_triangle(5)).
|
30
|
+
|
31
|
+
six_rows_test() ->
|
32
|
+
?assertEqual(
|
33
|
+
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1]],
|
34
|
+
pascals_triangle:gen_pascals_triangle(6)).
|
35
|
+
|
36
|
+
ten_rows_test() ->
|
37
|
+
?assertEqual(
|
38
|
+
[[1],
|
39
|
+
[1, 1],
|
40
|
+
[1, 2, 1],
|
41
|
+
[1, 3, 3, 1],
|
42
|
+
[1, 4, 6, 4, 1],
|
43
|
+
[1, 5, 10, 10, 5, 1],
|
44
|
+
[1, 6, 15, 20, 15, 6, 1],
|
45
|
+
[1, 7, 21, 35, 35, 21, 7, 1],
|
46
|
+
[1, 8, 28, 56, 70, 56, 28, 8, 1],
|
47
|
+
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]],
|
48
|
+
pascals_triangle:gen_pascals_triangle(10)).
|
49
|
+
|
50
|
+
negative_rows_test() ->
|
51
|
+
?assertEqual(-1, pascals_triangle:gen_pascals_triangle(-1)).
|
@@ -1,68 +1,48 @@
|
|
1
|
-
// This file was
|
1
|
+
// This file was auto-generated based on version 1.0.0 of the canonical data.
|
2
2
|
|
3
3
|
module DiffieHellmanTest
|
4
4
|
|
5
|
-
open System.Numerics
|
6
|
-
|
7
|
-
open Xunit
|
8
5
|
open FsUnit.Xunit
|
6
|
+
open Xunit
|
9
7
|
|
10
8
|
open DiffieHellman
|
11
9
|
|
12
10
|
[<Fact>]
|
13
|
-
let ``Private key in range`` () =
|
14
|
-
let
|
15
|
-
let privateKeys = [for _ in 0 .. 10 -> privateKey
|
16
|
-
privateKeys |> List.iter (fun x -> x |> should be (
|
17
|
-
privateKeys |> List.iter (fun x -> x |> should be (
|
11
|
+
let ``Private key is in range 1 .. p`` () =
|
12
|
+
let p = 7919I
|
13
|
+
let privateKeys = [for _ in 0 .. 10 -> privateKey p]
|
14
|
+
privateKeys |> List.iter (fun x -> x |> should be (greaterThan 1I))
|
15
|
+
privateKeys |> List.iter (fun x -> x |> should be (lessThan p))
|
18
16
|
|
19
|
-
// Note: due to the nature of randomness, there is always a chance that this test fails
|
20
|
-
// Be sure to check the actual generated values
|
21
|
-
[<Fact(Skip = "Remove to run test")>]
|
22
|
-
let ``Private key randomly generated`` () =
|
23
|
-
let primeP = 7919I
|
24
|
-
let privateKeys = [for _ in 0 .. 5 -> privateKey primeP]
|
25
|
-
privateKeys.Length |> should equal <| List.length (List.distinct privateKeys)
|
26
|
-
|
27
17
|
[<Fact(Skip = "Remove to run test")>]
|
28
|
-
let ``
|
29
|
-
let
|
30
|
-
let
|
31
|
-
|
32
|
-
|
33
|
-
let actual = publicKey primeP primeG privateKey
|
34
|
-
actual |> should equal 8I
|
18
|
+
let ``Private key is random`` () =
|
19
|
+
let p = 7919I
|
20
|
+
let privateKeys = [for _ in 0 .. 10 -> privateKey p]
|
21
|
+
List.distinct privateKeys |> List.length |> should equal (List.length privateKeys)
|
35
22
|
|
36
23
|
[<Fact(Skip = "Remove to run test")>]
|
37
|
-
let ``
|
38
|
-
let
|
39
|
-
let
|
24
|
+
let ``Can calculate public key using private key`` () =
|
25
|
+
let p = 23I
|
26
|
+
let g = 5I
|
40
27
|
let privateKey = 6I
|
41
|
-
|
42
|
-
let actual = secret primeP publicKey privateKey
|
43
|
-
actual |> should equal 2I
|
28
|
+
publicKey p g privateKey |> should equal 8I
|
44
29
|
|
45
30
|
[<Fact(Skip = "Remove to run test")>]
|
46
|
-
let ``
|
47
|
-
let
|
48
|
-
let
|
49
|
-
let
|
50
|
-
|
51
|
-
let actual = secret primeP publicKey privateKey
|
52
|
-
actual |> should equal expected
|
31
|
+
let ``Can calculate secret using other party's public key`` () =
|
32
|
+
let p = 23I
|
33
|
+
let theirPublicKey = 19I
|
34
|
+
let myPrivateKey = 6I
|
35
|
+
secret p theirPublicKey myPrivateKey |> should equal 2I
|
53
36
|
|
54
37
|
[<Fact(Skip = "Remove to run test")>]
|
55
|
-
let ``
|
56
|
-
let
|
57
|
-
let
|
58
|
-
|
59
|
-
let
|
60
|
-
let
|
61
|
-
|
62
|
-
let
|
63
|
-
let
|
64
|
-
|
65
|
-
let secretA = secret primeP publicKeyB privateKeyA
|
66
|
-
let secretB = secret primeP publicKeyA privateKeyB
|
38
|
+
let ``Key exchange`` () =
|
39
|
+
let p = 23I
|
40
|
+
let g = 5I
|
41
|
+
let alicePrivateKey = privateKey p
|
42
|
+
let alicePublicKey = publicKey p g alicePrivateKey
|
43
|
+
let bobPrivateKey = privateKey p
|
44
|
+
let bobPublicKey = publicKey p g bobPrivateKey
|
45
|
+
let secretA = secret p bobPublicKey alicePrivateKey
|
46
|
+
let secretB = secret p alicePublicKey bobPrivateKey
|
47
|
+
secretA |> should equal secretB
|
67
48
|
|
68
|
-
secretA |> should equal secretB
|
@@ -1,4 +1,4 @@
|
|
1
|
-
// This file was auto-generated based on version 1.
|
1
|
+
// This file was auto-generated based on version 1.1.0 of the canonical data.
|
2
2
|
|
3
3
|
module YachtTest
|
4
4
|
|
@@ -59,6 +59,10 @@ let ``Full house three small, two big`` () =
|
|
59
59
|
let ``Two pair is not a full house`` () =
|
60
60
|
score Category.FullHouse [2; 2; 4; 4; 5] |> should equal 0
|
61
61
|
|
62
|
+
[<Fact(Skip = "Remove to run test")>]
|
63
|
+
let ``Four of a kind is not a full house`` () =
|
64
|
+
score Category.FullHouse [1; 4; 4; 4; 4] |> should equal 0
|
65
|
+
|
62
66
|
[<Fact(Skip = "Remove to run test")>]
|
63
67
|
let ``Yacht is not a full house`` () =
|
64
68
|
score Category.FullHouse [2; 2; 2; 2; 2] |> should equal 0
|
@@ -431,6 +431,59 @@ type Diamond() =
|
|
431
431
|
type DifferenceOfSquares() =
|
432
432
|
inherit GeneratorExercise()
|
433
433
|
|
434
|
+
type DiffieHellman() =
|
435
|
+
inherit GeneratorExercise()
|
436
|
+
|
437
|
+
override __.RenderValue (canonicalDataCase, key, value) =
|
438
|
+
match canonicalDataCase.Property, value.Type with
|
439
|
+
| _, JTokenType.Integer ->
|
440
|
+
sprintf "%dI" (value.ToObject<int>())
|
441
|
+
| "keyExchange", _ ->
|
442
|
+
value.ToObject<string>().Replace(",", "").Replace("(", " ").Replace(")", "")
|
443
|
+
| _ ->
|
444
|
+
base.RenderValue(canonicalDataCase, key, value)
|
445
|
+
|
446
|
+
override __.RenderArrange canonicalDataCase =
|
447
|
+
let arrange = base.RenderArrange canonicalDataCase
|
448
|
+
|
449
|
+
match canonicalDataCase.Property with
|
450
|
+
| "privateKeyIsInRange" | "privateKeyIsRandom" ->
|
451
|
+
List.append arrange ["let privateKeys = [for _ in 0 .. 10 -> privateKey p]" ]
|
452
|
+
| _ -> arrange
|
453
|
+
|
454
|
+
override __.RenderAssert canonicalDataCase =
|
455
|
+
match canonicalDataCase.Property with
|
456
|
+
| "privateKeyIsInRange" ->
|
457
|
+
let greaterThan = canonicalDataCase.Expected.["greaterThan"].ToObject<int>()
|
458
|
+
let lessThan = canonicalDataCase.Expected.["lessThan"].ToObject<string>()
|
459
|
+
|
460
|
+
[ sprintf "privateKeys |> List.iter (fun x -> x |> should be (greaterThan %dI))" greaterThan;
|
461
|
+
sprintf "privateKeys |> List.iter (fun x -> x |> should be (lessThan %s))" lessThan ]
|
462
|
+
| "privateKeyIsRandom" ->
|
463
|
+
[ "List.distinct privateKeys |> List.length |> should equal (List.length privateKeys)" ]
|
464
|
+
| "keyExchange" ->
|
465
|
+
[ "secretA |> should equal secretB" ]
|
466
|
+
| _ -> base.RenderAssert canonicalDataCase
|
467
|
+
|
468
|
+
override __.MapCanonicalDataCase canonicalDataCase =
|
469
|
+
match canonicalDataCase.Property with
|
470
|
+
| "privateKeyIsInRange" | "privateKeyIsRandom" ->
|
471
|
+
{ canonicalDataCase with Input = Map.add "p" (JToken.Parse("7919")) canonicalDataCase.Input }
|
472
|
+
| _ -> base.MapCanonicalDataCase canonicalDataCase
|
473
|
+
|
474
|
+
override this.PropertiesWithIdentifier canonicalDataCase = this.PropertiesUsedAsSutParameter canonicalDataCase
|
475
|
+
|
476
|
+
override __.PropertiesUsedAsSutParameter canonicalDataCase =
|
477
|
+
match canonicalDataCase.Property with
|
478
|
+
| "publicKey" ->
|
479
|
+
["p"; "g"; "privateKey"]
|
480
|
+
| "secret" ->
|
481
|
+
["p"; "theirPublicKey"; "myPrivateKey"]
|
482
|
+
| "keyExchange" ->
|
483
|
+
["p"; "g"; "alicePrivateKey"; "alicePublicKey"; "bobPrivateKey"; "bobPublicKey"; "secretA"; "secretB"]
|
484
|
+
| _ ->
|
485
|
+
base.PropertiesUsedAsSutParameter canonicalDataCase
|
486
|
+
|
434
487
|
type Dominoes() =
|
435
488
|
inherit GeneratorExercise()
|
436
489
|
|
data/tracks/go/config.json
CHANGED
@@ -1060,6 +1060,18 @@
|
|
1060
1060
|
"unlocked_by": "tree-building",
|
1061
1061
|
"uuid": "7aa53a27-4ff3-4891-809f-2af728eb55a0"
|
1062
1062
|
},
|
1063
|
+
{
|
1064
|
+
"core": false,
|
1065
|
+
"difficulty": 4,
|
1066
|
+
"slug": "markdown",
|
1067
|
+
"topics": [
|
1068
|
+
"refactoring",
|
1069
|
+
"strings",
|
1070
|
+
"text_formatting"
|
1071
|
+
],
|
1072
|
+
"unlocked_by": "twelve-days",
|
1073
|
+
"uuid": "a7469032-c4d0-4e15-aa0c-4e9f6588b4c8"
|
1074
|
+
},
|
1063
1075
|
{
|
1064
1076
|
"core": false,
|
1065
1077
|
"difficulty": 4,
|
@@ -0,0 +1,59 @@
|
|
1
|
+
package main
|
2
|
+
|
3
|
+
import (
|
4
|
+
"log"
|
5
|
+
"text/template"
|
6
|
+
|
7
|
+
"../../../gen"
|
8
|
+
)
|
9
|
+
|
10
|
+
func main() {
|
11
|
+
t, err := template.New("").Parse(tmpl)
|
12
|
+
if err != nil {
|
13
|
+
log.Fatal(err)
|
14
|
+
}
|
15
|
+
var j js
|
16
|
+
if err := gen.Gen("markdown", &j, t); err != nil {
|
17
|
+
log.Fatal(err)
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
// The JSON structure we expect to be able to unmarshal into
|
22
|
+
type js struct {
|
23
|
+
Exercise string
|
24
|
+
Version string
|
25
|
+
Comments []string
|
26
|
+
Cases []oneCase
|
27
|
+
}
|
28
|
+
|
29
|
+
// Test cases
|
30
|
+
type oneCase struct {
|
31
|
+
Description string
|
32
|
+
Property string
|
33
|
+
Input struct {
|
34
|
+
Markdown string
|
35
|
+
}
|
36
|
+
Expected string
|
37
|
+
}
|
38
|
+
|
39
|
+
// Template to generate test cases.
|
40
|
+
var tmpl = `package markdown
|
41
|
+
|
42
|
+
{{.Header}}
|
43
|
+
|
44
|
+
{{range .J.Comments}}
|
45
|
+
// {{ . }}
|
46
|
+
{{end}}
|
47
|
+
|
48
|
+
var testCases = []struct {
|
49
|
+
description string
|
50
|
+
input string
|
51
|
+
expected string
|
52
|
+
}{ {{range .J.Cases}}
|
53
|
+
{
|
54
|
+
description: {{printf "%q" .Description}},
|
55
|
+
input: {{printf "%#v" .Input.Markdown}},
|
56
|
+
expected: {{printf "%#v" .Expected}},
|
57
|
+
},{{end}}
|
58
|
+
}
|
59
|
+
`
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Markdown
|
2
|
+
|
3
|
+
Refactor a Markdown parser.
|
4
|
+
|
5
|
+
The markdown exercise is a refactoring exercise. There is code that parses a
|
6
|
+
given string with [Markdown
|
7
|
+
syntax](https://guides.github.com/features/mastering-markdown/) and returns the
|
8
|
+
associated HTML for that string. Even though this code is confusingly written
|
9
|
+
and hard to follow, somehow it works and all the tests are passing! Your
|
10
|
+
challenge is to re-write this code to make it easier to read and maintain
|
11
|
+
while still making sure that all the tests keep passing.
|
12
|
+
|
13
|
+
It would be helpful if you made notes of what you did in your refactoring in
|
14
|
+
comments so reviewers can see that, but it isn't strictly necessary. The most
|
15
|
+
important thing is to make the code better!
|
16
|
+
|
17
|
+
## Running the tests
|
18
|
+
|
19
|
+
To run the tests run the command `go test` from within the exercise directory.
|
20
|
+
|
21
|
+
If the test suite contains benchmarks, you can run these with the `-bench`
|
22
|
+
flag:
|
23
|
+
|
24
|
+
go test -bench .
|
25
|
+
|
26
|
+
Keep in mind that each reviewer will run benchmarks on a different machine, with
|
27
|
+
different specs, so the results from these benchmark tests may vary.
|
28
|
+
|
29
|
+
## Further information
|
30
|
+
|
31
|
+
For more detailed information about the Go track, including how to get help if
|
32
|
+
you're having trouble, please visit the exercism.io [Go language page](http://exercism.io/languages/go/about).
|
33
|
+
|
34
|
+
## Submitting Incomplete Solutions
|
35
|
+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
@@ -0,0 +1,59 @@
|
|
1
|
+
package markdown
|
2
|
+
|
3
|
+
// Source: exercism/problem-specifications
|
4
|
+
// Commit: 9713d52 markdown: Apply new "input" policy
|
5
|
+
// Problem Specifications Version: 1.2.0
|
6
|
+
|
7
|
+
// Markdown is a shorthand for creating HTML from text strings.
|
8
|
+
|
9
|
+
var testCases = []struct {
|
10
|
+
description string
|
11
|
+
input string
|
12
|
+
expected string
|
13
|
+
}{
|
14
|
+
{
|
15
|
+
description: "parses normal text as a paragraph",
|
16
|
+
input: "This will be a paragraph",
|
17
|
+
expected: "<p>This will be a paragraph</p>",
|
18
|
+
},
|
19
|
+
{
|
20
|
+
description: "parsing italics",
|
21
|
+
input: "_This will be italic_",
|
22
|
+
expected: "<p><em>This will be italic</em></p>",
|
23
|
+
},
|
24
|
+
{
|
25
|
+
description: "parsing bold text",
|
26
|
+
input: "__This will be bold__",
|
27
|
+
expected: "<p><strong>This will be bold</strong></p>",
|
28
|
+
},
|
29
|
+
{
|
30
|
+
description: "mixed normal, italics and bold text",
|
31
|
+
input: "This will _be_ __mixed__",
|
32
|
+
expected: "<p>This will <em>be</em> <strong>mixed</strong></p>",
|
33
|
+
},
|
34
|
+
{
|
35
|
+
description: "with h1 header level",
|
36
|
+
input: "# This will be an h1",
|
37
|
+
expected: "<h1>This will be an h1</h1>",
|
38
|
+
},
|
39
|
+
{
|
40
|
+
description: "with h2 header level",
|
41
|
+
input: "## This will be an h2",
|
42
|
+
expected: "<h2>This will be an h2</h2>",
|
43
|
+
},
|
44
|
+
{
|
45
|
+
description: "with h6 header level",
|
46
|
+
input: "###### This will be an h6",
|
47
|
+
expected: "<h6>This will be an h6</h6>",
|
48
|
+
},
|
49
|
+
{
|
50
|
+
description: "unordered lists",
|
51
|
+
input: "* Item 1\n* Item 2",
|
52
|
+
expected: "<ul><li>Item 1</li><li>Item 2</li></ul>",
|
53
|
+
},
|
54
|
+
{
|
55
|
+
description: "With a little bit of everything",
|
56
|
+
input: "# Header!\n* __Bold Item__\n* _Italic Item_",
|
57
|
+
expected: "<h1>Header!</h1><ul><li><strong>Bold Item</strong></li><li><em>Italic Item</em></li></ul>",
|
58
|
+
},
|
59
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
package markdown
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
"fmt"
|
6
|
+
"regexp"
|
7
|
+
)
|
8
|
+
|
9
|
+
type charType int
|
10
|
+
|
11
|
+
// used to keep track of which tag to close
|
12
|
+
const (
|
13
|
+
none charType = iota
|
14
|
+
hash
|
15
|
+
star
|
16
|
+
)
|
17
|
+
|
18
|
+
// Render translates markdown to HTML
|
19
|
+
func Render(markdown string) (html string) {
|
20
|
+
//first the easy one, via regexp substitution
|
21
|
+
reStrong := regexp.MustCompile("(__)(.*)(__)")
|
22
|
+
s := reStrong.ReplaceAll([]byte(markdown), []byte("<strong>$2</strong>"))
|
23
|
+
reEm := regexp.MustCompile("(_)(.*)(_)")
|
24
|
+
s = reEm.ReplaceAll(s, []byte("<em>$2</em>"))
|
25
|
+
//now manage <li> and <hN>
|
26
|
+
var output bytes.Buffer
|
27
|
+
starcount := 0
|
28
|
+
hcount := 0
|
29
|
+
needtoClose := none
|
30
|
+
for i := 0; i < len(s); i++ {
|
31
|
+
switch s[i] {
|
32
|
+
case '#':
|
33
|
+
for s[i] == '#' {
|
34
|
+
hcount++
|
35
|
+
i++
|
36
|
+
}
|
37
|
+
output.WriteString(fmt.Sprintf("<h%d>", hcount))
|
38
|
+
needtoClose = hash
|
39
|
+
case '*':
|
40
|
+
if starcount == 0 {
|
41
|
+
output.WriteString("<ul>")
|
42
|
+
}
|
43
|
+
i++
|
44
|
+
starcount++
|
45
|
+
output.WriteString("<li>")
|
46
|
+
needtoClose = star
|
47
|
+
case '\n':
|
48
|
+
if needtoClose == hash {
|
49
|
+
output.WriteString(fmt.Sprintf("</h%d>", hcount))
|
50
|
+
}
|
51
|
+
if needtoClose == star {
|
52
|
+
output.WriteString("</li>")
|
53
|
+
}
|
54
|
+
|
55
|
+
default:
|
56
|
+
output.WriteByte(s[i])
|
57
|
+
|
58
|
+
}
|
59
|
+
}
|
60
|
+
if starcount > 0 || hcount > 0 {
|
61
|
+
if needtoClose == hash {
|
62
|
+
output.WriteString(fmt.Sprintf("</h%d>", hcount))
|
63
|
+
}
|
64
|
+
if needtoClose == star {
|
65
|
+
output.WriteString("</li></ul>")
|
66
|
+
}
|
67
|
+
return output.String()
|
68
|
+
}
|
69
|
+
return fmt.Sprintf("<p>%s</p>", string(s))
|
70
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
package markdown
|
2
|
+
|
3
|
+
// implementation to refactor
|
4
|
+
|
5
|
+
import (
|
6
|
+
"fmt"
|
7
|
+
"strings"
|
8
|
+
)
|
9
|
+
|
10
|
+
// Render translates markdown to HTML
|
11
|
+
func Render(markdown string) string {
|
12
|
+
header := 0
|
13
|
+
markdown = strings.Replace(markdown, "__", "<strong>", 1)
|
14
|
+
markdown = strings.Replace(markdown, "__", "</strong>", 1)
|
15
|
+
markdown = strings.Replace(markdown, "_", "<em>", 1)
|
16
|
+
markdown = strings.Replace(markdown, "_", "</em>", 1)
|
17
|
+
pos := 0
|
18
|
+
list := 0
|
19
|
+
html := ""
|
20
|
+
for {
|
21
|
+
char := markdown[pos]
|
22
|
+
if char == '#' {
|
23
|
+
for char == '#' {
|
24
|
+
header++
|
25
|
+
pos++
|
26
|
+
char = markdown[pos]
|
27
|
+
}
|
28
|
+
html += fmt.Sprintf("<h%d>", header)
|
29
|
+
pos++
|
30
|
+
continue
|
31
|
+
}
|
32
|
+
if char == '*' {
|
33
|
+
if list == 0 {
|
34
|
+
html += "<ul>"
|
35
|
+
}
|
36
|
+
html += "<li>"
|
37
|
+
list++
|
38
|
+
pos += 2
|
39
|
+
continue
|
40
|
+
}
|
41
|
+
if char == '\n' {
|
42
|
+
if list > 0 {
|
43
|
+
html += "</li>"
|
44
|
+
}
|
45
|
+
if header > 0 {
|
46
|
+
html += fmt.Sprintf("</h%d>", header)
|
47
|
+
header = 0
|
48
|
+
}
|
49
|
+
pos++
|
50
|
+
continue
|
51
|
+
}
|
52
|
+
html += string(char)
|
53
|
+
pos++
|
54
|
+
if pos >= len(markdown) {
|
55
|
+
break
|
56
|
+
}
|
57
|
+
}
|
58
|
+
if header > 0 {
|
59
|
+
return html + fmt.Sprintf("</h%d>", header)
|
60
|
+
}
|
61
|
+
if list > 0 {
|
62
|
+
return html + "</li></ul>"
|
63
|
+
}
|
64
|
+
return "<p>" + html + "</p>"
|
65
|
+
|
66
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
package markdown
|
2
|
+
|
3
|
+
import "testing"
|
4
|
+
|
5
|
+
func TestMarkdown(t *testing.T) {
|
6
|
+
for _, test := range testCases {
|
7
|
+
if html := Render(test.input); html != test.expected {
|
8
|
+
t.Fatalf("FAIL: Render(%q) = %q, want %q.", test.input, html, test.expected)
|
9
|
+
}
|
10
|
+
t.Logf("PASS: %s\n", test.description)
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
func BenchmarkMarkdown(b *testing.B) {
|
15
|
+
// Benchmark time to parse all the test cases
|
16
|
+
for i := 0; i < b.N; i++ {
|
17
|
+
for _, test := range testCases {
|
18
|
+
Render(test.input)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|