trackler 2.2.1.139 → 2.2.1.140
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|