trackler 2.0.1.0 → 2.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/common/exercises/isogram/canonical-data.json +42 -31
  3. data/common/exercises/leap/canonical-data.json +7 -7
  4. data/common/exercises/matrix/description.md +3 -3
  5. data/common/exercises/minesweeper/canonical-data.json +54 -2
  6. data/common/exercises/pangram/canonical-data.json +5 -0
  7. data/lib/trackler/version.rb +1 -1
  8. data/tracks/clojure/config.json +5 -0
  9. data/tracks/clojure/exercises/acronym/project.clj +4 -0
  10. data/tracks/clojure/exercises/acronym/src/example.clj +8 -0
  11. data/tracks/clojure/exercises/acronym/test/acronym_test.clj +12 -0
  12. data/tracks/crystal/config.json +6 -0
  13. data/tracks/crystal/exercises/binary/spec/binary_spec.cr +76 -0
  14. data/tracks/crystal/exercises/binary/src/example.cr +18 -0
  15. data/tracks/crystal/src/generator/exercises/binary.cr +42 -0
  16. data/tracks/csharp/docs/TESTS.md +6 -2
  17. data/tracks/go/config.json +5 -0
  18. data/tracks/go/exercises/perfect-numbers/example.go +41 -0
  19. data/tracks/go/exercises/perfect-numbers/perfect_numbers_test.go +41 -0
  20. data/tracks/groovy/.gitignore +1 -0
  21. data/tracks/groovy/SETUP.md +6 -54
  22. data/tracks/groovy/exercises/difference-of-squares/Example.groovy +17 -15
  23. data/tracks/groovy/exercises/difference-of-squares/SquaresSpec.groovy +42 -0
  24. data/tracks/groovy/exercises/gigasecond/Example.groovy +9 -7
  25. data/tracks/groovy/exercises/gigasecond/Gigasecond.groovy +5 -2
  26. data/tracks/groovy/exercises/gigasecond/GigasecondSpec.groovy +38 -0
  27. data/tracks/groovy/exercises/hamming/Example.groovy +7 -5
  28. data/tracks/groovy/exercises/hamming/HammingSpec.groovy +50 -0
  29. data/tracks/groovy/exercises/hello-world/Example.groovy +5 -3
  30. data/tracks/groovy/exercises/hello-world/HelloWorld.groovy +5 -3
  31. data/tracks/groovy/exercises/hello-world/HelloWorldSpec.groovy +23 -0
  32. data/tracks/groovy/exercises/raindrops/Example.groovy +8 -8
  33. data/tracks/groovy/exercises/raindrops/RaindropsSpec.groovy +98 -0
  34. data/tracks/groovy/exercises/rna-transcription/ComplementSpec.groovy +51 -0
  35. data/tracks/groovy/exercises/rna-transcription/Example.groovy +19 -14
  36. data/tracks/haskell/.travis.yml +1 -1
  37. data/tracks/haskell/common/stack.yaml +1 -0
  38. data/tracks/haskell/config.json +1 -0
  39. data/tracks/haskell/exercises/pov/stack.yaml +1 -0
  40. data/tracks/haskell/exercises/robot-name/examples/success-standard/src/Robot.hs +1 -1
  41. data/tracks/java/bin/journey-test.sh +9 -4
  42. data/tracks/objective-c/Dangerfile +0 -2
  43. data/tracks/ocaml/exercises/hello-world/test.ml +6 -9
  44. data/tracks/ocaml/tools/test-generator/.merlin +1 -0
  45. data/tracks/ocaml/tools/test-generator/Makefile +4 -4
  46. data/tracks/ocaml/tools/test-generator/{src → interfaces}/codegen.mli +5 -1
  47. data/tracks/ocaml/tools/test-generator/{src → interfaces}/parser.mli +0 -0
  48. data/tracks/ocaml/tools/test-generator/interfaces/special_cases.mli +7 -0
  49. data/tracks/ocaml/tools/test-generator/interfaces/template.mli +11 -0
  50. data/tracks/ocaml/tools/test-generator/interfaces/test_generator.mli +3 -0
  51. data/tracks/ocaml/tools/test-generator/{src → interfaces}/utils.mli +0 -0
  52. data/tracks/ocaml/tools/test-generator/src/codegen.ml +6 -6
  53. data/tracks/ocaml/tools/test-generator/src/template.ml +24 -0
  54. data/tracks/ocaml/tools/test-generator/src/test_generator.ml +10 -26
  55. data/tracks/ocaml/tools/test-generator/test/all_tests.ml +2 -2
  56. data/tracks/ocaml/tools/test-generator/test/codegen_test.ml +1 -1
  57. data/tracks/ocaml/tools/test-generator/test/sample_template.txt +14 -0
  58. data/tracks/ocaml/tools/test-generator/test/template_test.ml +26 -0
  59. data/tracks/perl6/exercises/binary/binary.t +1 -1
  60. data/tracks/perl6/exercises/raindrops/Example.pm +2 -2
  61. data/tracks/perl6/exercises/raindrops/raindrops.t +19 -24
  62. data/tracks/php/.travis.yml +1 -0
  63. data/tracks/php/Makefile +26 -15
  64. data/tracks/php/README.md +19 -0
  65. data/tracks/ruby/README.md +10 -0
  66. data/tracks/rust/config.json +1 -0
  67. data/tracks/rust/exercises/alphametics/Cargo-example.toml +7 -0
  68. data/tracks/rust/exercises/alphametics/Cargo.lock +4 -0
  69. data/tracks/rust/exercises/alphametics/Cargo.toml +3 -0
  70. data/tracks/rust/exercises/alphametics/example.rs +76 -0
  71. data/tracks/rust/exercises/alphametics/src/lib.rs +5 -0
  72. data/tracks/rust/exercises/alphametics/tests/alphametics.rs +62 -0
  73. data/tracks/rust/problems.md +1 -0
  74. data/tracks/scala/exercises/dominoes/src/test/scala/DominoesSuite.scala +1 -1
  75. data/tracks/swift/Dangerfile +0 -2
  76. metadata +32 -39
  77. data/tracks/groovy/exercises/difference-of-squares/SquaresTest.groovy +0 -49
  78. data/tracks/groovy/exercises/gigasecond/GigasecondTest.groovy +0 -29
  79. data/tracks/groovy/exercises/hamming/HammingTest.groovy +0 -56
  80. data/tracks/groovy/exercises/hello-world/HelloWorldTest.groovy +0 -19
  81. data/tracks/groovy/exercises/raindrops/RaindropsTest.groovy +0 -21
  82. data/tracks/groovy/exercises/rna-transcription/ComplementTest.groovy +0 -55
  83. data/tracks/haskell/exercises/pov/stack.yaml +0 -1
  84. data/tracks/java/exercises/anagram/src/main/java/.keep +0 -0
  85. data/tracks/java/exercises/beer-song/src/example/java/.keep +0 -0
  86. data/tracks/java/exercises/beer-song/src/main/java/.keep +0 -0
  87. data/tracks/java/exercises/beer-song/src/test/java/.keep +0 -0
  88. data/tracks/java/exercises/difference-of-squares/src/example/java/.keep +0 -0
  89. data/tracks/java/exercises/difference-of-squares/src/main/java/.keep +0 -0
  90. data/tracks/java/exercises/etl/src/main/java/.keep +0 -0
  91. data/tracks/java/exercises/etl/src/test/java/.keep +0 -0
  92. data/tracks/java/exercises/grade-school/src/test/java/.keep +0 -0
  93. data/tracks/java/exercises/hamming/src/main/java/.keep +0 -0
  94. data/tracks/java/exercises/hello-world/src/main/java/.keep +0 -0
  95. data/tracks/java/exercises/hexadecimal/src/test/java/.keep +0 -0
  96. data/tracks/java/exercises/linked-list/src/example/java/.keep +0 -0
  97. data/tracks/java/exercises/linked-list/src/test/java/.keep +0 -0
  98. data/tracks/java/exercises/meetup/src/main/java/.keep +0 -0
  99. data/tracks/java/exercises/meetup/src/test/java/.keep +0 -0
  100. data/tracks/java/exercises/nth-prime/src/example/java/.keep +0 -0
  101. data/tracks/java/exercises/nth-prime/src/test/java/.keep +0 -0
  102. data/tracks/java/exercises/nucleotide-count/src/main/java/.keep +0 -0
  103. data/tracks/java/exercises/nucleotide-count/src/test/java/.keep +0 -0
  104. data/tracks/java/exercises/pangram/src/test/java/.keep +0 -0
  105. data/tracks/java/exercises/pascals-triangle/src/test/java/.keep +0 -0
  106. data/tracks/java/exercises/series/src/main/java/.keep +0 -0
  107. data/tracks/java/exercises/series/src/test/java/.keep +0 -0
  108. data/tracks/java/exercises/triangle/src/main/java/.keep +0 -0
  109. data/tracks/ocaml/tools/test-generator/src/special_cases.mli +0 -13
  110. data/tracks/ocaml/tools/test-generator/src/test_generator.mli +0 -6
  111. data/tracks/ocaml/tools/test-generator/test/test_generator_test.ml +0 -11
@@ -1,11 +1,11 @@
1
1
  test: test_gen.native
2
2
  @./all_tests.native
3
3
 
4
- test_gen.native: all_tests.native
5
- @ocamlbuild -use-ocamlfind -tag thread -tag short_paths -cflags -strict-sequence -r -pkg core -pkg yojson -pkg ppx_deriving.eq -pkg ppx_deriving.show -Is "src" test_gen.native
4
+ test_gen.native: all_tests.native src/*.ml interfaces/*.mli test/*.ml
5
+ @ocamlbuild -use-ocamlfind -tag thread -tag short_paths -cflags -strict-sequence -r -pkg core -pkg yojson -pkg ppx_deriving -pkg ppx_deriving.eq -pkg ppx_deriving.show -Is "src,interfaces" test_gen.native
6
6
 
7
- all_tests.native: src/*.ml src/*.mli test/*.ml
8
- @ocamlbuild -use-ocamlfind -tag thread -tag short_paths -cflags -strict-sequence -r -pkg core -pkg oUnit -pkg yojson -pkg ppx_deriving.eq -pkg ppx_deriving.show -Is "src,test" all_tests.native
7
+ all_tests.native: src/*.ml test/*.ml
8
+ @ocamlbuild -use-ocamlfind -tag thread -tag short_paths -cflags -strict-sequence -r -pkg core -pkg oUnit -pkg yojson -pkg ppx_deriving -pkg ppx_deriving.eq -pkg ppx_deriving.show -Is "src,test" all_tests.native
9
9
 
10
10
  clean:
11
11
  rm -rf _build
@@ -6,4 +6,8 @@ type fixup_parameter_function = key: string -> value: parameter -> string
6
6
 
7
7
  type edit_parameters_function = (string * string) list -> (string * string) list
8
8
 
9
- val generate_code : fixup_parameter_function -> edit_parameters_function -> string -> case list -> (string list, string) Result.t
9
+ type subst = Subst of string
10
+
11
+ val subst_to_string : subst -> string
12
+
13
+ val generate_code : fixup_parameter_function -> edit_parameters_function -> string -> case list -> (subst list, string) Result.t
@@ -0,0 +1,7 @@
1
+ open Core.Std
2
+
3
+ open Model
4
+
5
+ val fixup : stringify:(parameter -> string) -> slug:string -> key:string -> value:parameter -> string
6
+
7
+ val edit : slug: string -> (string * string) list -> (string * string) list
@@ -0,0 +1,11 @@
1
+ open Core.Std
2
+
3
+ open Codegen
4
+
5
+ type t = {start: int; finish: int; file_text: string; template: string}
6
+
7
+ val fill : int -> int -> template: string -> subst list -> string
8
+
9
+ val find_template : template_text: string -> (int * int * string) option
10
+
11
+ val show : t -> string
@@ -0,0 +1,3 @@
1
+ open Core.Std
2
+
3
+ val run : templates_folder: string -> canonical_data_folder: string -> output_folder: string -> unit
@@ -6,19 +6,19 @@ type fixup_parameter_function = key: string -> value: parameter -> string
6
6
 
7
7
  type edit_parameters_function = (string * string) list -> (string * string) list
8
8
 
9
- let parameters_to_string pp: string =
10
- List.map ~f:(fun (k,v) -> "(" ^ k ^ "," ^ v ^ ")") pp
11
- |> String.concat ~sep:";"
9
+ type subst = Subst of string [@@deriving eq, show]
10
+
11
+ let subst_to_string (Subst s) = s
12
12
 
13
13
  let replace_key (key: string) (value: string) (target: string): string =
14
- String.substr_replace_all target ~pattern:("$" ^ key) ~with_:value
14
+ String.substr_replace_all ~pattern:("$" ^ key) ~with_:value target
15
15
 
16
- let rec replace_keys (f: fixup_parameter_function) (ed: edit_parameters_function) (s: string) (c: case): string =
16
+ let rec replace_keys (f: fixup_parameter_function) (ed: edit_parameters_function) (s: string) (c: case): subst =
17
17
  let s = replace_key "description" c.description s in
18
18
  let expected = f ~key:"expected" ~value:c.expected in
19
19
  let s = replace_key "expected" expected s in
20
20
  let parameter_strings = ed @@ List.map ~f:(fun (k,p) -> (k,parameter_to_string p)) c.parameters in
21
- List.fold parameter_strings ~init:s ~f:(fun s (k,v) -> replace_key k v s)
21
+ List.fold parameter_strings ~init:(Subst s) ~f:(fun (Subst s) (k,v) -> Subst (replace_key k v s))
22
22
 
23
23
  let generate_code (f: fixup_parameter_function) (ed: edit_parameters_function) template cases =
24
24
  Ok (List.map cases ~f:(replace_keys f ed template))
@@ -0,0 +1,24 @@
1
+ open Core.Std
2
+
3
+ open Codegen
4
+ open Utils
5
+
6
+ (* start and finish are the lines where the generated code is *)
7
+ type t = {start: int; finish: int; file_text: string; template: string} [@@deriving eq, show]
8
+
9
+ let find_template ~(template_text: string): t option =
10
+ let open Option.Monad_infix in
11
+ let str_contains pattern s = String.substr_index s ~pattern |> Option.is_some in
12
+ let lines = String.split_lines template_text |> List.to_array in
13
+ let start_index = find_arrayi lines ~f:(str_contains "(* GENERATED-CODE") |> Option.map ~f:fst in
14
+ let finish_index = (start_index >>= (fun start -> find_arrayi ~start lines ~f:(str_contains "END GENERATED-CODE *"))) |> Option.map ~f:fst in
15
+ let template_lines = Option.map2 start_index finish_index (fun s -> Array.slice lines (s+1)) in
16
+ Option.map2 start_index template_lines ~f:(fun s l -> {start=s; finish=(s + 1 + Array.length l); file_text = template_text; template=String.concat_array l ~sep:"\n"})
17
+
18
+ let fill (template: t) (substs: subst list): string =
19
+ let lines = String.split_lines template.file_text |> List.to_array in
20
+ let before = Array.slice lines 0 template.start in
21
+ let subst = Array.of_list (List.map ~f:subst_to_string substs) in
22
+ let after = Array.slice lines (template.finish + 1) (Array.length lines) in
23
+ let join = String.concat_array ~sep:"\n" in
24
+ String.concat [join before; join subst; join after] ~sep:"\n"
@@ -4,23 +4,7 @@ open Model
4
4
  open Utils
5
5
  open Codegen
6
6
  open Special_cases
7
-
8
- let find_template ~(template_text: string): (int * int * string) option =
9
- let open Option.Monad_infix in
10
- let str_contains pattern s = String.substr_index s ~pattern |> Option.is_some in
11
- let lines = String.split_lines template_text |> List.to_array in
12
- let start_index = find_arrayi lines ~f:(str_contains "(* GENERATED-CODE") |> Option.map ~f:fst in
13
- let finish_index = (start_index >>= (fun start -> find_arrayi ~start lines ~f:(str_contains "END GENERATED-CODE *"))) |> Option.map ~f:fst in
14
- let template_lines = Option.map2 start_index finish_index (fun s -> Array.slice lines (s+1)) in
15
- Option.map2 start_index template_lines ~f:(fun s l -> (s, s + 1 + Array.length l, String.concat_array l ~sep:"\n"))
16
-
17
- let splice_in_filled_in_code (start: int) (finish: int) ~(template: string) (substs: string list): string =
18
- let lines = String.split_lines template |> List.to_array in
19
- let before = Array.slice lines 0 start in
20
- let subst = Array.of_list substs in
21
- let after = Array.slice lines (finish + 1) (Array.length lines) in
22
- let join = String.concat_array ~sep:"\n" in
23
- String.concat [join before; join subst; join after] ~sep:"\n"
7
+ open Template
24
8
 
25
9
  type content = string
26
10
 
@@ -30,23 +14,23 @@ let find_nested_files (name: string) (base: string): (string * content) list =
30
14
  |> List.filter ~f:(fun slug -> Sys.file_exists_exn (base ^ "/" ^ slug ^ "/" ^ name))
31
15
  |> List.map ~f:(fun slug -> (slug, In_channel.read_all (base ^ "/" ^ slug ^ "/" ^ name)))
32
16
 
33
- let find_templates = find_nested_files "template.ml"
17
+ let find_template_files = find_nested_files "template.ml"
34
18
 
35
19
  let find_canonical_data_files = find_nested_files "canonical-data.json"
36
20
 
37
- let combine_files (templates: (string * content) list) (canonical_data: (string * content) list): (string * content * content) list =
38
- List.filter_map templates ~f:(fun (n,t) -> (List.Assoc.find canonical_data ~equal:String.equal n |> Option.map ~f:(fun c -> (n,t,c))))
21
+ let combine_files (template_files: (string * content) list) (canonical_data_files: (string * content) list): (string * content * content) list =
22
+ List.filter_map template_files ~f:(fun (n,t) -> (List.Assoc.find canonical_data_files ~equal:String.equal n |> Option.map ~f:(fun c -> (n,t,c))))
39
23
 
40
24
  let generate_code ~slug ~template_file ~canonical_data_file =
41
25
  let template = find_template template_file in
42
- let template = Result.of_option template "cannot find a template" in
26
+ let template = Result.of_option template ("cannot recognize file for " ^ slug ^ " as a template") in
43
27
  let cases = parse_json_text canonical_data_file in
44
28
  let cases = Result.map_error cases show_error in
45
29
  let open Result.Monad_infix in
46
- template >>= fun (s,e,template) ->
30
+ template >>= fun template ->
47
31
  cases >>= fun cs ->
48
- let substs = Result.ok_or_failwith @@ generate_code (fixup ~stringify:parameter_to_string ~slug) (edit ~slug) template cs in
49
- Result.return (splice_in_filled_in_code s e ~template:template_file substs)
32
+ let substs = Result.ok_or_failwith @@ generate_code (fixup ~stringify:parameter_to_string ~slug) (edit ~slug) template.template cs in
33
+ Result.return (fill template substs)
50
34
 
51
35
  let output_tests (files: (string * content * content) list) (output_folder: string): unit =
52
36
  let output_filepath name = output_folder ^ "/" ^ name ^ "/test.ml" in
@@ -56,7 +40,7 @@ let output_tests (files: (string * content * content) list) (output_folder: stri
56
40
  List.iter files ~f:output1
57
41
 
58
42
  let run ~(templates_folder: string) ~(canonical_data_folder: string) ~(output_folder: string) =
59
- let templates = find_templates templates_folder in
43
+ let template_files = find_template_files templates_folder in
60
44
  let canonical_data_files = find_canonical_data_files canonical_data_folder in
61
- let combined = combine_files templates canonical_data_files in
45
+ let combined = combine_files template_files canonical_data_files in
62
46
  output_tests combined output_folder
@@ -4,8 +4,8 @@ open Codegen_test
4
4
  open Model_test
5
5
  open Parser_test
6
6
  open Special_cases_test
7
- open Test_generator_test
7
+ open Template_test
8
8
  open Utils_test
9
9
 
10
10
  let () =
11
- run_test_tt_main ("tests" >:::List.concat [codegen_tests; model_tests; parser_tests; special_cases_tests; test_generator_tests; utils_tests])
11
+ run_test_tt_main ("tests" >:::List.concat [codegen_tests; model_tests; parser_tests; special_cases_tests; template_tests; utils_tests])
@@ -9,7 +9,7 @@ let fixup ~key ~value = parameter_to_string value
9
9
  let edit = Fn.id
10
10
  let assert_gen exp cases = assert_equal exp
11
11
  ~printer:(fun xs -> "[" ^ (String.concat ~sep:";" xs) ^ "]")
12
- (Result.ok_or_failwith @@ generate_code fixup edit leap_template cases)
12
+ (Result.ok_or_failwith @@ generate_code fixup edit leap_template cases |> List.map ~f:subst_to_string)
13
13
  let ae exp cases _test_ctxt = assert_gen exp cases
14
14
 
15
15
  let codegen_tests = [
@@ -0,0 +1,14 @@
1
+ open Core.Std
2
+ open OUnit2
3
+ open Hello_world
4
+
5
+ let ae exp got _test_ctxt = assert_equal ~printer:String.to_string exp got
6
+
7
+ let tests = [
8
+ (* GENERATED-CODE
9
+ code
10
+ END GENERATED-CODE *)
11
+ ]
12
+
13
+ let () =
14
+ run_test_tt_main ("Hello World tests" >::: tests)
@@ -0,0 +1,26 @@
1
+ open Core.Std
2
+ open OUnit2
3
+ open Codegen
4
+ open Template
5
+
6
+ let printer = Option.value_map ~f:show ~default:"None"
7
+
8
+ let template_tests = [
9
+ "if there is no generated code marker in a string then find_template returns None" >:: (fun _ctx ->
10
+ assert_equal None @@ find_template ~template_text:"some code block"
11
+ );
12
+
13
+ "if there is generated code marker in a string then find_template returns where it is" >:: (fun _ctx ->
14
+ let template_text = In_channel.read_all "test/sample_template.txt" in
15
+ assert_equal ~printer (Some {start=7; finish=9; file_text=template_text; template="code"}) @@ find_template ~template_text
16
+ );
17
+
18
+ "fills in lines of a template" >:: (fun _ctx ->
19
+ let template_text = In_channel.read_all "test/sample_template.txt" in
20
+ let template = {start=7; finish=9; file_text=template_text;template="code"} in
21
+ let filled = fill template [Subst "line1"; Subst "line2"] in
22
+ let pattern = " (* GENERATED-CODE\ncode\n END GENERATED-CODE *)" in
23
+ let expected = (String.substr_replace_all ~pattern ~with_:"line1\nline2" template_text) in
24
+ assert_equal ~printer:Fn.id expected (filled ^ "\n")
25
+ );
26
+ ]
@@ -6,7 +6,7 @@ use lib IO::Path.new($?FILE).parent.path;
6
6
  plan 10;
7
7
 
8
8
  BEGIN {
9
- my $module = %*ENV{'EXERCISM'} ?? 'Example' !! 'Anagram';
9
+ my $module = %*ENV{'EXERCISM'} ?? 'Example' !! 'Binary';
10
10
  EVAL("use $module")
11
11
  };
12
12
 
@@ -1,9 +1,9 @@
1
1
  class Raindrops {
2
- method convert ($num) {
2
+ method convert (Int:D $num --> Str:D) {
3
3
  my $str = '';
4
4
  $str ~= "Pling" if $num % 3 == 0;
5
5
  $str ~= "Plang" if $num % 5 == 0;
6
6
  $str ~= "Plong" if $num % 7 == 0;
7
- return $str ?? $str !! $num;
7
+ return $str ?? $str !! $num.Str;
8
8
  }
9
9
  }
@@ -3,31 +3,26 @@ use v6;
3
3
  use Test;
4
4
  use lib IO::Path.new($?FILE).parent.path;
5
5
 
6
- plan 18;
7
-
8
6
  BEGIN {
9
- my $module = %*ENV{'EXERCISM'} ?? 'Example' !! 'Raindrops';
10
- EVAL("use $module")
11
- };
12
-
13
- pass 'Load module';
14
-
7
+ plan 23;
8
+ eval-lives-ok %*ENV<EXERCISM>.so ?? 'use Example' !! 'use Raindrops', 'Module loaded';
9
+ }
15
10
 
16
11
  ok Raindrops.can('convert'), 'Class Raindrops has convert method';
17
12
 
18
- is Raindrops.convert(1), 1, "test_1";
19
- is Raindrops.convert(3), "Pling", "test_3";
20
- is Raindrops.convert(5), "Plang", "test_5";
21
- is Raindrops.convert(7), "Plong", "test_7";
22
- is Raindrops.convert(6), "Pling", "test_6";
23
- is Raindrops.convert(9), "Pling", "test_9";
24
- is Raindrops.convert(10), "Plang", "test_10";
25
- is Raindrops.convert(14), "Plong", "test_14";
26
- is Raindrops.convert(15), "PlingPlang", "test_15";
27
- is Raindrops.convert(21), "PlingPlong", "test_21";
28
- is Raindrops.convert(25), "Plang", "test_25";
29
- is Raindrops.convert(35), "PlangPlong", "test_35";
30
- is Raindrops.convert(49), "Plong", "test_49";
31
- is Raindrops.convert(52), 52, "test_52";
32
- is Raindrops.convert(105), "PlingPlangPlong", "test_105";
33
- is Raindrops.convert(12121), 12121, "test_12121";
13
+ my @ints = 1, 8, 52;
14
+ my @plings = 3, 6, 9, 27;
15
+ my @plangs = 5, 10, 25, 3125;
16
+ my @plongs = 7, 14, 49;
17
+
18
+ for @ints {
19
+ is Raindrops.convert($_), $_, "$_ is not a factor of 3, 5 or 7";
20
+ isa-ok Raindrops.convert($_), Str, "$_ gives a string";
21
+ }
22
+ is Raindrops.convert($_), 'Pling', "$_ is a factor of 3, not 5 or 7" for @plings;
23
+ is Raindrops.convert($_), 'Plang', "$_ is a factor of 5, not 3 or 7" for @plangs;
24
+ is Raindrops.convert($_), 'Plong', "$_ is a factor of 7, not 3 or 5" for @plongs;
25
+ is Raindrops.convert(15), 'PlingPlang', '15 is a factor of 3 and 5, not 7';
26
+ is Raindrops.convert(21), 'PlingPlong', '21 is a factor of 3 and 7, not 5';
27
+ is Raindrops.convert(35), 'PlangPlong', '35 is a factor of 5 and 7, not 3';
28
+ is Raindrops.convert(105), 'PlingPlangPlong', '105 is a factor of 3, 5 and 7';
@@ -3,6 +3,7 @@ language: php
3
3
  php:
4
4
  - 7.0
5
5
  script:
6
+ - make install
6
7
  - make style-check
7
8
  - make test
8
9
  - "./bin/fetch-configlet"
data/tracks/php/Makefile CHANGED
@@ -12,32 +12,43 @@ FILEEXT := "php"
12
12
  EXAMPLE := "example.$(FILEEXT)"
13
13
  TSTFILE := "$(ASSIGNMENT)_test.$(FILEEXT)"
14
14
 
15
- # development dependencies
16
- bin/phpunit.phar:
17
- @wget --no-check-certificate https://phar.phpunit.de/phpunit.phar -O $@
18
- chmod +x $@
15
+ default:
16
+ wget --no-check-certificate https://phar.phpunit.de/phpunit.phar -O bin/phpunit.phar
17
+ chmod +x bin/phpunit.phar
18
+ @wget --no-check-certificate https://github.com/squizlabs/PHP_CodeSniffer/releases/download/2.0.0a2/phpcs.phar -O bin/phpcs.phar
19
+ chmod +x bin/phpcs.phar
19
20
 
20
- bin/phpcs.phar:
21
- @wget --no-check-certificate https://github.com/squizlabs/PHP_CodeSniffer/releases/download/2.0.0a2/phpcs.phar -O $@
22
- chmod +x $@
21
+ help: ## Prints this help
22
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
23
23
 
24
- # single test
25
- test-assignment: bin/phpunit.phar
24
+ install: ## install development dependencies
25
+ wget --no-check-certificate https://phar.phpunit.de/phpunit.phar -O bin/phpunit.phar
26
+ chmod +x bin/phpunit.phar
27
+
28
+ @wget --no-check-certificate https://github.com/squizlabs/PHP_CodeSniffer/releases/download/2.0.0a2/phpcs.phar -O bin/phpcs.phar
29
+ chmod +x bin/phpcs.phar
30
+
31
+ install-test: ## install test dependency: phpunit.phar
32
+ @wget --no-check-certificate https://phar.phpunit.de/phpunit.phar -O bin/phpunit.phar
33
+ chmod +x bin/phpunit.phar
34
+
35
+ install-style: ## install style checker dependency: phpcs.phar
36
+ @wget --no-check-certificate https://phar.phpunit.de/phpunit.phar -O bin/phpcs.phar
37
+ chmod +x bin/phpcs.phar
38
+
39
+ test-assignment: bin/phpunit.phar ## run single test using ASSIGNMENTS: test-assignment ASSIGNMENT=wordy
26
40
  @echo "running tests for: $(ASSIGNMENT)"
27
41
  @cat ./exercises/$(ASSIGNMENT)/$(TSTFILE) | sed '/markTestSkipped()/d' > $(OUTDIR)/$(TSTFILE)
28
42
  @cp ./exercises/$(ASSIGNMENT)/$(EXAMPLE) $(OUTDIR)/$(ASSIGNMENT).$(FILEEXT)
29
43
  @bin/phpunit.phar --no-configuration $(OUTDIR)/$(TSTFILE)
30
44
 
31
- # all tests
32
- test:
45
+ test: ## run all tests
33
46
  @for assignment in $(ASSIGNMENTS); do ASSIGNMENT=$$assignment $(MAKE) -s test-assignment || exit 1; done
34
47
 
35
- # style check single test
36
- style-check-assignment: bin/phpcs.phar
48
+ style-check-assignment: bin/phpcs.phar ## run style check single test using ASSIGNMENTS: style-check-assignment ASSIGNMENT=wordy
37
49
  @echo "checking $(ASSIGNMENT) against xPHP code standards"
38
50
  @bin/phpcs.phar -sp --standard=phpcs-xphp.xml ./exercises/$(ASSIGNMENT)
39
51
 
40
- # style c heck all tests
41
- style-check:
52
+ style-check: ## run style check all tests
42
53
  @for assignment in $(ASSIGNMENTS); do ASSIGNMENT=$$assignment $(MAKE) -s style-check-assignment || exit 1; done
43
54
 
data/tracks/php/README.md CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  Exercism exercises in PHP
6
6
 
7
+ ## Install Dependencies
8
+
9
+ ### All dependencies
10
+
11
+ % make install
12
+
13
+ ### Only tests dependencies
14
+
15
+ % make install-test
16
+
17
+ ### Only style-check dependencies
18
+
19
+ % make install-style
20
+
7
21
  ## Running Unit Test Suite
8
22
 
9
23
  ### All Assignments
@@ -24,6 +38,11 @@ Exercism exercises in PHP
24
38
 
25
39
  % make style-check-assignment ASSIGNMENT=wordy
26
40
 
41
+ ## Help
42
+ If you need command line help for run make commands
43
+
44
+ % make
45
+
27
46
  ## Contributing
28
47
 
29
48
  - Follow the [PSR-2] coding style (xPHP uses a slightly [modified] version of [PSR-2]).
@@ -108,6 +108,16 @@ then additional inputs/outputs should be submitted to the x-common repository.
108
108
  Changes to the test suite (style, boilerplate, etc) will probably have to be made to
109
109
  `example.tt`.
110
110
 
111
+ ### Exercise Generators
112
+
113
+ If you wish to create a new generator, or edit an existing one, the generators currently live in the lib directory and are named `$PROBLEM_cases.rb`. For example, the hamming generator is `lib/hamming_cases.rb`.
114
+
115
+ All generators currently adhere to a common public interface, and must define the following three methods:
116
+
117
+ - `test_name` - Returns the name of the test (i.e `test_one_equals_one`)
118
+ - `workload` - Returns the main syntax for the test. This will vary depending on the test generator and its underlying implementation
119
+ - `skipped` - Returns skip syntax (i.e. `skip` or `# skip`)
120
+
111
121
  ## Pull Requests
112
122
 
113
123
  We welcome pull requests that provide fixes to existing test suites (missing
@@ -39,6 +39,7 @@
39
39
  "wordy",
40
40
  "tournament",
41
41
  "custom-set",
42
+ "alphametics",
42
43
  "anagram",
43
44
  "nucleotide-codons",
44
45
  "robot-name",
@@ -0,0 +1,7 @@
1
+ [package]
2
+ name = "alphametics"
3
+ version = "0.0.0"
4
+
5
+ [dependencies]
6
+ itertools = "0.5"
7
+ permutohedron = "0.2"
@@ -0,0 +1,4 @@
1
+ [root]
2
+ name = "alphametics"
3
+ version = "0.0.0"
4
+
@@ -0,0 +1,3 @@
1
+ [package]
2
+ name = "alphametics"
3
+ version = "0.0.0"
@@ -0,0 +1,76 @@
1
+ // This is a brute-force solution, use `cargo test --release` for faster testing
2
+
3
+ extern crate itertools;
4
+ extern crate permutohedron;
5
+
6
+ use itertools::Itertools;
7
+ use permutohedron::Heap as Permutations;
8
+
9
+
10
+ use std::collections::HashMap;
11
+ use std::collections::HashSet;
12
+ use std::char;
13
+
14
+ fn test_equation(puzzle: &str, substitutions: &HashMap<char, u8>) -> bool {
15
+ // Create a new String with characters changed to numbers
16
+ let puzzle: String = puzzle.chars()
17
+ .map(|c| {
18
+ if let Some(&n) = substitutions.get(&c) {
19
+ // If the character is in the substitutions, get the number and
20
+ // convert it to a char
21
+ char::from_digit(n as u32, 10).unwrap()
22
+ } else {
23
+ // Otherwise just copy over the character
24
+ c
25
+ }
26
+ })
27
+ .collect();
28
+
29
+ // Split the puzzle into left and right side
30
+ let equation: Vec<&str> = puzzle.split("==").collect();
31
+
32
+ // Parse the number on the right side
33
+ let right = equation[1].trim().parse::<u32>().unwrap();
34
+
35
+ // Sum the parts on the left side
36
+ let left: u32 = equation[0].split('+').map(str::trim).map(|n| n.parse::<u32>().unwrap()).sum();
37
+
38
+ // Create a String with just the numbers and spaces
39
+ let just_numbers =
40
+ puzzle.chars().filter(|c| c.is_digit(10) || c.is_whitespace()).collect::<String>();
41
+ // Split this into the numbers and check every number's first character
42
+ let no_leading_zeroes = just_numbers.split_whitespace()
43
+ .all(|number| number.chars().next().unwrap() != '0');
44
+
45
+ // Return true if left and right side is equal and the equation doesnt
46
+ // contain leading zeroes.
47
+ left == right && no_leading_zeroes
48
+ }
49
+
50
+
51
+ pub fn solve(puzzle: &str) -> Option<HashMap<char, u8>> {
52
+ // Get unique letters from the puzzle
53
+ let letters: HashSet<char> =
54
+ puzzle.chars().filter(|&c| c.is_alphabetic() && c.is_uppercase()).collect();
55
+ let letters: Vec<char> = letters.into_iter().collect();
56
+
57
+ // All available numbers for substitution
58
+ let numbers: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
59
+
60
+ // Iterate every combination with the length of unique letters in the puzzle
61
+ for combinations in numbers.iter().combinations(letters.len()) {
62
+ let mut c = combinations;
63
+ let permutations = Permutations::new(&mut c);
64
+ // Iterate every permutation of a letter combination
65
+ for p in permutations {
66
+ let substitution: HashMap<char, u8> =
67
+ letters.iter().zip(p).map(|(&c, &n)| (c, n)).collect();
68
+ if test_equation(puzzle, &substitution) {
69
+ // We found a good substitution
70
+ return Some(substitution);
71
+ }
72
+ }
73
+ }
74
+ // If we tested every combination and did not found a solution then return None
75
+ None
76
+ }
@@ -0,0 +1,5 @@
1
+ use std::collections::HashMap;
2
+
3
+ pub fn solve(puzzle: &str) -> Option<HashMap<char, u8>> {
4
+ unimplemented!()
5
+ }
@@ -0,0 +1,62 @@
1
+ extern crate alphametics;
2
+ use std::collections::HashMap;
3
+
4
+ fn assert_alphametic_solution_eq(puzzle: &str, solution: &[(char, u8)]) {
5
+ let answer = alphametics::solve(puzzle).unwrap();
6
+ let solution: HashMap<char, u8> = solution.iter().cloned().collect();
7
+ assert_eq!(answer, solution);
8
+ }
9
+
10
+ #[test]
11
+ fn test_with_three_letters() {
12
+ assert_alphametic_solution_eq("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]);
13
+ }
14
+
15
+ #[test]
16
+ #[ignore]
17
+ fn test_must_have_unique_value_for_each_letter() {
18
+ let answer = alphametics::solve("A == B");
19
+ assert_eq!(answer, None);
20
+ }
21
+
22
+ #[test]
23
+ #[ignore]
24
+ fn test_leading_zero_solution_is_invalid() {
25
+ let answer = alphametics::solve("ACA + DD == BD");
26
+ assert_eq!(answer, None);
27
+ }
28
+
29
+ #[test]
30
+ #[ignore]
31
+ fn test_puzzle_with_four_letters() {
32
+ assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]);
33
+ }
34
+
35
+ #[test]
36
+ #[ignore]
37
+ fn test_puzzle_with_six_letters() {
38
+ assert_alphametic_solution_eq("NO + NO + TOO == LATE",
39
+ &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)]);
40
+ }
41
+
42
+ #[test]
43
+ #[ignore]
44
+ fn test_puzzle_with_seven_letters() {
45
+ assert_alphametic_solution_eq("HE + SEES + THE == LIGHT",
46
+ &[('E', 4), ('G', 2), ('H', 5), ('I', 0), ('L', 1), ('S', 9), ('T', 7)]);
47
+ }
48
+
49
+ #[test]
50
+ #[ignore]
51
+ fn test_puzzle_with_eight_letters() {
52
+ assert_alphametic_solution_eq("SEND + MORE == MONEY",
53
+ &[('S', 9), ('E', 5), ('N', 6), ('D', 7), ('M', 1), ('O', 0), ('R', 8), ('Y', 2)]);
54
+ }
55
+
56
+ #[test]
57
+ #[ignore]
58
+ fn test_puzzle_with_ten_letters() {
59
+ assert_alphametic_solution_eq("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE",
60
+ &[('A', 5), ('D', 3), ('E', 4), ('F', 7), ('G', 8), ('N', 0), ('O', 2), ('R', 1),
61
+ ('S', 6), ('T', 9)]);
62
+ }
@@ -58,6 +58,7 @@ phone-number | option, format, unwrap_or, iters, match
58
58
  wordy | Result, string parsing, operators (optional)
59
59
  tournament | enum, sorting, hashmap, structs
60
60
  custom-set | generic over type, vector, equality, struct
61
+ alphametics | string parsing, combinations, math, external crates (optional)
61
62
 
62
63
  ## Rust Gets Strange
63
64
 
@@ -71,7 +71,7 @@ class DominoesSuite extends FunSuite with Matchers {
71
71
 
72
72
  private def checkChain(result: List[(Int, Int)], input: List[(Int, Int)]): Unit = {
73
73
  def sortDomino(ab: (Int, Int)): (Int, Int) =
74
- if (x._1 > x._2) x.swap else x
74
+ if (ab._1 > ab._2) ab.swap else ab
75
75
  def consecutivesShouldMatch(dominoes: List[((Int, Int), Int)]): Unit =
76
76
  dominoes.tails foreach {
77
77
  case (a@(_,x), i1)::(b@(y,_), i2)::_ =>
@@ -1,5 +1,3 @@
1
- message("Thank you for submitting this PR.")
2
-
3
1
  # Ensure a clean commits history
4
2
  if git.commits.any? { |c| c.message =~ /^Merge branch '#{github.branch_for_base}'/ }
5
3
  fail('Please rebase to get rid of the merge commits in this PR')