trackler 2.2.1.62 → 2.2.1.63

Sign up to get free protection for your applications and to get access to all the features.
Files changed (247) hide show
  1. checksums.yaml +4 -4
  2. data/lib/trackler/version.rb +1 -1
  3. data/problem-specifications/exercises/all-your-base/canonical-data.json +20 -29
  4. data/problem-specifications/exercises/forth/canonical-data.json +52 -5
  5. data/problem-specifications/exercises/isbn-verifier/canonical-data.json +15 -15
  6. data/problem-specifications/exercises/protein-translation/canonical-data.json +153 -0
  7. data/problem-specifications/exercises/reverse-string/canonical-data.json +39 -0
  8. data/problem-specifications/exercises/reverse-string/description.md +5 -0
  9. data/problem-specifications/exercises/reverse-string/metadata.yml +5 -0
  10. data/tracks/c/config.json +42 -41
  11. data/tracks/csharp/config.json +13 -0
  12. data/tracks/csharp/exercises/Exercises.sln +6 -0
  13. data/tracks/csharp/exercises/isbn-verifier/Example.cs +27 -0
  14. data/tracks/csharp/exercises/isbn-verifier/IsbnVerifier.cs +9 -0
  15. data/tracks/csharp/exercises/isbn-verifier/IsbnVerifier.csproj +19 -0
  16. data/tracks/csharp/exercises/isbn-verifier/IsbnVerifierTest.cs +84 -0
  17. data/tracks/csharp/exercises/isbn-verifier/README.md +40 -0
  18. data/tracks/csharp/generators/Exercises/IsbnVerifier.cs +15 -0
  19. data/tracks/delphi/config.json +28 -28
  20. data/tracks/erlang/config.json +10 -0
  21. data/tracks/erlang/exercises/accumulate/rebar.config +1 -1
  22. data/tracks/erlang/exercises/accumulate/test/accumulate_tests.erl +9 -7
  23. data/tracks/erlang/exercises/all-your-base/rebar.config +1 -1
  24. data/tracks/erlang/exercises/all-your-base/test/all_your_base_tests.erl +26 -24
  25. data/tracks/erlang/exercises/allergies/rebar.config +1 -1
  26. data/tracks/erlang/exercises/allergies/test/allergies_tests.erl +19 -17
  27. data/tracks/erlang/exercises/anagram/rebar.config +1 -3
  28. data/tracks/erlang/exercises/anagram/test/anagram_tests.erl +14 -12
  29. data/tracks/erlang/exercises/atbash-cipher/rebar.config +1 -1
  30. data/tracks/erlang/exercises/atbash-cipher/test/atbash_cipher_tests.erl +7 -5
  31. data/tracks/erlang/exercises/bank-account/rebar.config +1 -1
  32. data/tracks/erlang/exercises/bank-account/test/bank_account_tests.erl +52 -51
  33. data/tracks/erlang/exercises/beer-song/rebar.config +30 -0
  34. data/tracks/erlang/exercises/beer-song/test/beer_song_tests.erl +11 -10
  35. data/tracks/erlang/exercises/bob/rebar.config +1 -1
  36. data/tracks/erlang/exercises/bob/test/bob_tests.erl +33 -35
  37. data/tracks/erlang/exercises/circular-buffer/rebar.config +1 -1
  38. data/tracks/erlang/exercises/circular-buffer/test/circular_buffer_tests.erl +33 -32
  39. data/tracks/erlang/exercises/clock/rebar.config +1 -1
  40. data/tracks/erlang/exercises/clock/test/clock_tests.erl +57 -50
  41. data/tracks/erlang/exercises/collatz-conjecture/rebar.config +1 -1
  42. data/tracks/erlang/exercises/collatz-conjecture/test/collatz_conjecture_tests.erl +11 -9
  43. data/tracks/erlang/exercises/complex-numbers/rebar.config +1 -1
  44. data/tracks/erlang/exercises/complex-numbers/test/complex_numbers_tests.erl +102 -76
  45. data/tracks/erlang/exercises/custom-set/rebar.config +1 -1
  46. data/tracks/erlang/exercises/custom-set/test/custom_set_tests.erl +100 -109
  47. data/tracks/erlang/exercises/difference-of-squares/rebar.config +1 -1
  48. data/tracks/erlang/exercises/difference-of-squares/test/difference_of_squares_tests.erl +8 -7
  49. data/tracks/erlang/exercises/etl/rebar.config +1 -1
  50. data/tracks/erlang/exercises/etl/test/etl_tests.erl +6 -5
  51. data/tracks/erlang/exercises/gigasecond/rebar.config +1 -1
  52. data/tracks/erlang/exercises/gigasecond/test/gigasecond_tests.erl +9 -8
  53. data/tracks/erlang/exercises/grade-school/rebar.config +1 -1
  54. data/tracks/erlang/exercises/grade-school/test/grade_school_tests.erl +27 -23
  55. data/tracks/erlang/exercises/grains/rebar.config +1 -1
  56. data/tracks/erlang/exercises/grains/test/grains_tests.erl +7 -6
  57. data/tracks/erlang/exercises/hamming/rebar.config +1 -1
  58. data/tracks/erlang/exercises/hamming/test/hamming_tests.erl +20 -23
  59. data/tracks/erlang/exercises/hello-world/rebar.config +1 -1
  60. data/tracks/erlang/exercises/hello-world/test/hello_world_tests.erl +6 -4
  61. data/tracks/erlang/exercises/isogram/rebar.config +1 -1
  62. data/tracks/erlang/exercises/isogram/test/isogram_tests.erl +14 -12
  63. data/tracks/erlang/exercises/largest-series-product/rebar.config +1 -1
  64. data/tracks/erlang/exercises/largest-series-product/test/largest_series_product_tests.erl +22 -21
  65. data/tracks/erlang/exercises/leap/rebar.config +1 -1
  66. data/tracks/erlang/exercises/leap/test/leap_tests.erl +8 -7
  67. data/tracks/erlang/exercises/luhn/rebar.config +1 -1
  68. data/tracks/erlang/exercises/luhn/test/luhn_tests.erl +18 -16
  69. data/tracks/erlang/exercises/meetup/rebar.config +1 -1
  70. data/tracks/erlang/exercises/meetup/test/meetup_tests.erl +96 -95
  71. data/tracks/erlang/exercises/nucleotide-count/rebar.config +1 -1
  72. data/tracks/erlang/exercises/nucleotide-count/test/nucleotide_count_tests.erl +12 -11
  73. data/tracks/erlang/exercises/pangram/README.md +60 -0
  74. data/tracks/erlang/exercises/{accumulate → pangram}/include/exercism.hrl +0 -0
  75. data/tracks/erlang/exercises/{beer-song/rebar.conf → pangram/rebar.config} +0 -0
  76. data/tracks/erlang/exercises/pangram/src/example.erl +11 -0
  77. data/tracks/erlang/exercises/pangram/src/pangram.app.src +9 -0
  78. data/tracks/erlang/exercises/pangram/src/pangram.erl +8 -0
  79. data/tracks/erlang/exercises/pangram/test/pangram_tests.erl +38 -0
  80. data/tracks/erlang/exercises/parallel-letter-frequency/rebar.config +1 -1
  81. data/tracks/erlang/exercises/parallel-letter-frequency/test/parallel_letter_frequency_tests.erl +7 -6
  82. data/tracks/erlang/exercises/phone-number/rebar.config +1 -1
  83. data/tracks/erlang/exercises/phone-number/test/phone_number_tests.erl +13 -12
  84. data/tracks/erlang/exercises/rna-transcription/rebar.config +1 -1
  85. data/tracks/erlang/exercises/rna-transcription/test/rna_transcription_tests.erl +13 -11
  86. data/tracks/erlang/exercises/robot-simulator/rebar.config +1 -1
  87. data/tracks/erlang/exercises/robot-simulator/test/robot_simulator_tests.erl +70 -69
  88. data/tracks/erlang/exercises/roman-numerals/rebar.config +1 -1
  89. data/tracks/erlang/exercises/roman-numerals/test/roman_numerals_tests.erl +6 -5
  90. data/tracks/erlang/exercises/rotational-cipher/rebar.config +1 -8
  91. data/tracks/erlang/exercises/rotational-cipher/test/rotational_cipher_tests.erl +29 -27
  92. data/tracks/erlang/exercises/scrabble-score/rebar.config +1 -1
  93. data/tracks/erlang/exercises/scrabble-score/test/scrabble_score_tests.erl +6 -5
  94. data/tracks/erlang/exercises/series/rebar.config +1 -1
  95. data/tracks/erlang/exercises/series/test/series_tests.erl +6 -5
  96. data/tracks/erlang/exercises/sieve/rebar.config +1 -1
  97. data/tracks/erlang/exercises/sieve/test/sieve_tests.erl +10 -8
  98. data/tracks/erlang/exercises/space-age/rebar.config +1 -1
  99. data/tracks/erlang/exercises/space-age/test/space_age_tests.erl +20 -19
  100. data/tracks/erlang/exercises/spiral-matrix/rebar.config +1 -1
  101. data/tracks/erlang/exercises/spiral-matrix/test/spiral_matrix_tests.erl +12 -10
  102. data/tracks/erlang/exercises/strain/rebar.config +1 -1
  103. data/tracks/erlang/exercises/strain/test/strain_tests.erl +17 -16
  104. data/tracks/erlang/exercises/sum-of-multiples/rebar.config +1 -1
  105. data/tracks/erlang/exercises/sum-of-multiples/test/sum_of_multiples_tests.erl +16 -15
  106. data/tracks/erlang/exercises/triangle/rebar.config +1 -1
  107. data/tracks/erlang/exercises/triangle/test/triangle_tests.erl +20 -19
  108. data/tracks/erlang/exercises/two-fer/rebar.config +1 -1
  109. data/tracks/erlang/exercises/two-fer/test/two_fer_tests.erl +8 -7
  110. data/tracks/erlang/exercises/word-count/rebar.config +1 -1
  111. data/tracks/erlang/exercises/word-count/test/word_count_tests.erl +6 -5
  112. data/tracks/erlang/exercises/zipper/rebar.config +1 -1
  113. data/tracks/erlang/exercises/zipper/test/zipper_tests.erl +47 -46
  114. data/tracks/erlang/testgen/src/tgen.erl +8 -6
  115. data/tracks/erlang/testgen/src/tgen_bob.erl +1 -1
  116. data/tracks/erlang/testgen/src/tgen_collatz-conjecture.erl +2 -2
  117. data/tracks/erlang/testgen/src/tgen_complex-numbers.erl +11 -11
  118. data/tracks/erlang/testgen/src/tgen_custom-set.erl +14 -14
  119. data/tracks/erlang/testgen/src/tgen_hamming.erl +2 -2
  120. data/tracks/erlang/testgen/src/tgen_hello-world.erl +1 -1
  121. data/tracks/erlang/testgen/src/tgen_leap.erl +1 -1
  122. data/tracks/erlang/testgen/src/tgen_rna-transcription.erl +2 -2
  123. data/tracks/erlang/testgen/src/tgs.erl +6 -0
  124. data/tracks/fsharp/exercises/beer-song/BeerSong.fs +1 -5
  125. data/tracks/fsharp/exercises/beer-song/BeerSongTest.fs +342 -20
  126. data/tracks/fsharp/exercises/beer-song/Example.fs +16 -10
  127. data/tracks/fsharp/exercises/food-chain/Example.fs +1 -1
  128. data/tracks/fsharp/exercises/food-chain/FoodChain.fs +1 -3
  129. data/tracks/fsharp/exercises/food-chain/FoodChainTest.fs +11 -11
  130. data/tracks/fsharp/exercises/house/Example.fs +1 -1
  131. data/tracks/fsharp/exercises/house/House.fs +1 -3
  132. data/tracks/fsharp/exercises/house/HouseTest.fs +202 -202
  133. data/tracks/fsharp/exercises/proverb/Example.fs +9 -9
  134. data/tracks/fsharp/exercises/proverb/Proverb.fs +1 -3
  135. data/tracks/fsharp/exercises/proverb/ProverbTest.fs +50 -21
  136. data/tracks/fsharp/exercises/twelve-days/Example.fs +4 -4
  137. data/tracks/fsharp/exercises/twelve-days/TwelveDays.fs +2 -6
  138. data/tracks/fsharp/exercises/twelve-days/TwelveDaysTest.fs +81 -57
  139. data/tracks/fsharp/generators/Generators.fs +39 -15
  140. data/tracks/go/bin/run-generators +5 -1
  141. data/tracks/go/config.json +14 -2
  142. data/tracks/go/exercises/accumulate/example.go +2 -1
  143. data/tracks/go/exercises/zebra-puzzle/.meta/hints.md +24 -0
  144. data/tracks/go/exercises/zebra-puzzle/README.md +76 -0
  145. data/tracks/go/exercises/zebra-puzzle/example.go +256 -0
  146. data/tracks/go/exercises/zebra-puzzle/zebra_puzzle_test.go +18 -0
  147. data/tracks/haskell/exercises/forth/package.yaml +1 -1
  148. data/tracks/haskell/exercises/forth/test/Tests.hs +1 -4
  149. data/tracks/haskell/exercises/pascals-triangle/package.yaml +1 -1
  150. data/tracks/haskell/exercises/pascals-triangle/test/Tests.hs +22 -1
  151. data/tracks/java/POLICIES.md +3 -3
  152. data/tracks/java/config.json +21 -2
  153. data/tracks/java/exercises/atbash-cipher/.meta/src/reference/java/Atbash.java +12 -12
  154. data/tracks/java/exercises/protein-translation/.meta/src/reference/java/ProteinTranslator.java +47 -0
  155. data/tracks/java/exercises/protein-translation/.meta/version +1 -0
  156. data/tracks/java/exercises/protein-translation/README.md +58 -0
  157. data/tracks/java/exercises/protein-translation/build.gradle +18 -0
  158. data/tracks/java/exercises/protein-translation/src/main/java/ProteinTranslator.java +8 -0
  159. data/tracks/java/exercises/protein-translation/src/test/java/ProteinTranslatorTest.java +179 -0
  160. data/tracks/java/exercises/settings.gradle +1 -0
  161. data/tracks/javascript/.eslintignore +2 -2
  162. data/tracks/javascript/exercises/accumulate/example.js +1 -2
  163. data/tracks/javascript/exercises/grains/example.js +6 -3
  164. data/tracks/kotlin/exercises/beer-song/.meta/src/reference/kotlin/BeerSong.kt +3 -3
  165. data/tracks/kotlin/exercises/beer-song/.meta/version +1 -1
  166. data/tracks/kotlin/exercises/beer-song/README.md +1 -1
  167. data/tracks/kotlin/exercises/beer-song/src/test/kotlin/BeerSongTest.kt +6 -6
  168. data/tracks/kotlin/exercises/forth/.meta/version +1 -1
  169. data/tracks/kotlin/exercises/forth/src/test/kotlin/ForthEvaluatorTest.kt +0 -7
  170. data/tracks/kotlin/exercises/meetup/README.md +16 -12
  171. data/tracks/kotlin/exercises/nth-prime/.meta/src/reference/kotlin/Prime.kt +7 -2
  172. data/tracks/kotlin/exercises/nth-prime/.meta/version +1 -1
  173. data/tracks/kotlin/exercises/nth-prime/src/test/kotlin/PrimeTest.kt +10 -1
  174. data/tracks/kotlin/exercises/nucleotide-count/README.md +2 -2
  175. data/tracks/kotlin/exercises/pascals-triangle/.meta/src/reference/kotlin/PascalsTriangle.kt +1 -1
  176. data/tracks/kotlin/exercises/pascals-triangle/.meta/version +1 -1
  177. data/tracks/kotlin/exercises/pascals-triangle/src/test/kotlin/PascalsTriangleTest.kt +58 -1
  178. data/tracks/kotlin/exercises/sum-of-multiples/README.md +3 -3
  179. data/tracks/ocaml/exercises/forth/test.ml +1 -3
  180. data/tracks/ocaml/exercises/rectangles/example.ml +11 -11
  181. data/tracks/php/exercises/bob/example.php +2 -2
  182. data/tracks/purescript/config.json +3 -3
  183. data/tracks/python/config.json +34 -22
  184. data/tracks/python/exercises/alphametics/example.py +90 -34
  185. data/tracks/python/exercises/ocr-numbers/example.py +18 -21
  186. data/tracks/python/exercises/ocr-numbers/ocr_numbers.py +1 -5
  187. data/tracks/python/exercises/ocr-numbers/ocr_numbers_test.py +124 -106
  188. data/tracks/python/exercises/simple-linked-list/README.md +49 -0
  189. data/tracks/python/exercises/simple-linked-list/example.py +67 -0
  190. data/tracks/python/exercises/simple-linked-list/hints.md +10 -0
  191. data/tracks/python/exercises/simple-linked-list/simple_linked_list.py +33 -0
  192. data/tracks/python/exercises/simple-linked-list/simple_linked_list_test.py +112 -0
  193. data/tracks/rust/README.md +2 -0
  194. data/tracks/rust/bin/init_exercise.py +586 -0
  195. data/tracks/rust/config.json +20 -10
  196. data/tracks/rust/exercises/book-store/.gitignore +3 -0
  197. data/tracks/rust/exercises/book-store/Cargo-example.toml +7 -0
  198. data/tracks/rust/exercises/book-store/Cargo.toml +6 -0
  199. data/tracks/rust/exercises/book-store/README.md +107 -0
  200. data/tracks/rust/exercises/book-store/example.rs +187 -0
  201. data/tracks/rust/exercises/book-store/src/lib.rs +3 -0
  202. data/tracks/rust/exercises/book-store/tests/book-store.rs +130 -0
  203. data/tracks/sml/config.json +8 -8
  204. data/tracks/typescript/config.json +6 -6
  205. metadata +43 -46
  206. data/tracks/erlang/exercises/all-your-base/include/exercism.hrl +0 -11
  207. data/tracks/erlang/exercises/allergies/include/exercism.hrl +0 -11
  208. data/tracks/erlang/exercises/anagram/include/exercism.hrl +0 -11
  209. data/tracks/erlang/exercises/atbash-cipher/include/exercism.hrl +0 -11
  210. data/tracks/erlang/exercises/bank-account/include/exercism.hrl +0 -11
  211. data/tracks/erlang/exercises/beer-song/include/exercism.hrl +0 -11
  212. data/tracks/erlang/exercises/bob/include/exercism.hrl +0 -11
  213. data/tracks/erlang/exercises/circular-buffer/include/exercism.hrl +0 -11
  214. data/tracks/erlang/exercises/clock/include/exercism.hrl +0 -11
  215. data/tracks/erlang/exercises/collatz-conjecture/include/exercism.hrl +0 -11
  216. data/tracks/erlang/exercises/complex-numbers/include/exercism.hrl +0 -11
  217. data/tracks/erlang/exercises/custom-set/include/exercism.hrl +0 -11
  218. data/tracks/erlang/exercises/difference-of-squares/include/exercism.hrl +0 -11
  219. data/tracks/erlang/exercises/etl/include/exercism.hrl +0 -11
  220. data/tracks/erlang/exercises/gigasecond/include/exercism.hrl +0 -11
  221. data/tracks/erlang/exercises/grade-school/include/exercism.hrl +0 -11
  222. data/tracks/erlang/exercises/grains/include/exercism.hrl +0 -11
  223. data/tracks/erlang/exercises/hamming/include/exercism.hrl +0 -11
  224. data/tracks/erlang/exercises/hello-world/include/exercism.hrl +0 -11
  225. data/tracks/erlang/exercises/isogram/include/exercism.hrl +0 -11
  226. data/tracks/erlang/exercises/largest-series-product/include/exercism.hrl +0 -11
  227. data/tracks/erlang/exercises/leap/include/exercism.hrl +0 -11
  228. data/tracks/erlang/exercises/luhn/include/exercism.hrl +0 -11
  229. data/tracks/erlang/exercises/meetup/include/exercism.hrl +0 -11
  230. data/tracks/erlang/exercises/nucleotide-count/include/exercism.hrl +0 -11
  231. data/tracks/erlang/exercises/parallel-letter-frequency/include/exercism.hrl +0 -11
  232. data/tracks/erlang/exercises/phone-number/include/exercism.hrl +0 -11
  233. data/tracks/erlang/exercises/rna-transcription/include/exercism.hrl +0 -11
  234. data/tracks/erlang/exercises/robot-simulator/include/exercism.hrl +0 -11
  235. data/tracks/erlang/exercises/roman-numerals/include/exercism.hrl +0 -11
  236. data/tracks/erlang/exercises/rotational-cipher/include/exercism.hrl +0 -11
  237. data/tracks/erlang/exercises/scrabble-score/include/exercism.hrl +0 -11
  238. data/tracks/erlang/exercises/series/include/exercism.hrl +0 -11
  239. data/tracks/erlang/exercises/sieve/include/exercism.hrl +0 -11
  240. data/tracks/erlang/exercises/space-age/include/exercism.hrl +0 -11
  241. data/tracks/erlang/exercises/spiral-matrix/include/exercism.hrl +0 -11
  242. data/tracks/erlang/exercises/strain/include/exercism.hrl +0 -11
  243. data/tracks/erlang/exercises/sum-of-multiples/include/exercism.hrl +0 -11
  244. data/tracks/erlang/exercises/triangle/include/exercism.hrl +0 -11
  245. data/tracks/erlang/exercises/two-fer/include/exercism.hrl +0 -11
  246. data/tracks/erlang/exercises/word-count/include/exercism.hrl +0 -11
  247. data/tracks/erlang/exercises/zipper/include/exercism.hrl +0 -11
@@ -0,0 +1,586 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Script to initialize an exercise for Exercism's Rust track
4
+
5
+ Why a Python script in the Rust track repo? Distribution.
6
+ A rust program would either need to be precompiled for various
7
+ platforms, and available for download (in which case it wouldn't
8
+ conveniently work in the repository), or would need to be included
9
+ as a sub-crate and compiled locally. A python script can simply
10
+ be a single file and depend on the user's system Python, if desired.
11
+
12
+ This module requires Python3.5 or newer
13
+ """
14
+ from __future__ import print_function
15
+
16
+ try:
17
+ import collections
18
+ import json
19
+ import os
20
+ import shlex
21
+ import subprocess
22
+ import string
23
+ import sys
24
+ import urllib.request
25
+
26
+ from contextlib import contextmanager
27
+ from uuid import uuid4
28
+ except ImportError:
29
+ print("This script requires Python 3.5 or higher", file=sys.stderr)
30
+ # exiting like this isn't great for library use, but at least it's a quick fail
31
+ sys.exit(1)
32
+
33
+ # check version info
34
+ if sys.version_info[0] != 3 or sys.version_info[1] < 5:
35
+ print("This script requires Python 3.5 or higher", file=sys.stderr)
36
+ # exiting like this isn't great for library use, but at least it's a quick fail
37
+ sys.exit(1)
38
+
39
+
40
+ def output_of(cmd, check_returncode=True):
41
+ "Return the stdout of the given command"
42
+ sp = subprocess.run(shlex.split(cmd),
43
+ stdout=subprocess.PIPE,
44
+ universal_newlines=True)
45
+ if check_returncode:
46
+ sp.check_returncode()
47
+ return sp.stdout.strip()
48
+
49
+
50
+ os.chdir(os.path.dirname(__file__))
51
+ REPO_ROOT = output_of('git rev-parse --show-toplevel')
52
+ EXERCISES = os.path.join(REPO_ROOT, 'exercises')
53
+ ITEM_NAME_CHARS = {c for c in string.ascii_lowercase + string.digits + '_'}
54
+ VERSION = "1.0.0"
55
+
56
+
57
+ def to_item_name(description):
58
+ "Produce a valid rust item name from arbitrary inputs"
59
+ item = description.lower().replace(' ', '_')
60
+ item = [c for c in item if c in ITEM_NAME_CHARS]
61
+ while len(item) > 0 and item[0] in string.digits:
62
+ item = item[1:]
63
+ if len(item) == 0:
64
+ raise ValueError("Could not produce an item name from " + description)
65
+ return ''.join(item)
66
+
67
+
68
+ def to_crate_name(name):
69
+ return name.replace('-', '_')
70
+
71
+
72
+ def url_for(name, file):
73
+ return (
74
+ "https://raw.githubusercontent.com/exercism/problem-specifications"
75
+ f"/master/exercises/{name}/{file}"
76
+ )
77
+
78
+
79
+ def get_problem_specification(name):
80
+ """
81
+ Try to get problem specifications for the exercise of the given name.
82
+
83
+ If the problem specifications repo doesn't exist or the exercise does not
84
+ exist within the specifications repo, returns None.
85
+ Otherwise, returns a dict, of which the values might be None or str.
86
+ """
87
+ try:
88
+ with urllib.request.urlopen(url_for(name, 'canonical-data.json')) as response:
89
+ return json.loads(response.read())
90
+ except (urllib.request.URLError, json.JSONDecodeError):
91
+ pass
92
+
93
+
94
+ @contextmanager
95
+ def inside(path):
96
+ cwd = os.getcwd()
97
+ os.chdir(path)
98
+ try:
99
+ yield
100
+ finally:
101
+ os.chdir(cwd)
102
+
103
+
104
+ def make_exercise(name, use_maplit):
105
+ "Make a new exercise with the specified name"
106
+ with inside(EXERCISES):
107
+ if os.path.exists(name):
108
+ print(f"{name} already exists; aborting", file=sys.stderr)
109
+ sys.exit(1)
110
+ subprocess.run(['cargo', 'new', name])
111
+ exercise_dir = os.path.join(EXERCISES, name)
112
+ # blank out the default lib.rs
113
+ with inside(exercise_dir):
114
+ with open('.gitignore', 'w') as gitignore:
115
+ print("# Ignore Cargo.lock if creating a library", file=gitignore)
116
+ print("# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock", file=gitignore)
117
+ print("Cargo.lock", file=gitignore)
118
+ with open(os.path.join('src', 'lib.rs'), 'w') as lib_rs:
119
+ lib_rs.truncate()
120
+ if use_maplit:
121
+ with open('Cargo.toml', 'a') as cargo_toml:
122
+ print("maplit = \"1.0.0\"", file=cargo_toml)
123
+ os.mkdir('tests')
124
+ with inside('tests'):
125
+ with open(f'{name}.rs', 'w') as tests_rs:
126
+ if use_maplit:
127
+ print("#[macro_use] extern crate maplit;", file=tests_rs)
128
+ print(f"extern crate {to_crate_name(name)};", file=tests_rs)
129
+ print(f"use {to_crate_name(name)}::*;", file=tests_rs)
130
+ print(file=tests_rs)
131
+ with open('example.rs', 'w') as example_rs:
132
+ print(f"//! Example implementation for {name}", file=example_rs)
133
+ print('//!', file=example_rs)
134
+ print("//! - Implement the solution to your exercise here.", file=example_rs)
135
+ print("//! - Put the stubs for any tested functions in `src/lib.rs`,", file=example_rs)
136
+ print("//! whose variable names are `_` and", file=example_rs)
137
+ print("//! whose contents are `unimplemented!()`.", file=example_rs)
138
+ print("//! - If your example implementation has dependencies, copy", file=example_rs)
139
+ print("//! `Cargo.toml` into `Cargo-example.toml` and then make", file=example_rs)
140
+ print("//! any modifications necessary to the latter so your example will run.", file=example_rs)
141
+ print("//! - Test your example by running `../../bin/test-exercise`", file=example_rs)
142
+
143
+ cd = get_problem_specification(name)
144
+ if cd is None:
145
+ print(f"No problem specification for {name} found")
146
+ make_new_exercise(name, exercise_dir)
147
+ generate_readme(name, get_problem_specification=False)
148
+ else:
149
+ make_exercise_with_specifications(name, exercise_dir, cd, use_maplit)
150
+ generate_readme(name, get_problem_specification=True)
151
+
152
+
153
+ def make_new_exercise(name, exercise_dir):
154
+ print("Creating new exercise from scratch...")
155
+ with inside(exercise_dir):
156
+ os.mkdir('.meta')
157
+ with inside('.meta'):
158
+ with open('description.md', 'w') as description:
159
+ print("Describe your exercise here", file=description)
160
+ print(file=description)
161
+ print("Don't forget that `README.md` is automatically generated; update this within `.meta\description.md`.", file=description)
162
+ with open('metadata.yml', 'w') as metadata:
163
+ print("---", file=metadata)
164
+ print(f"blurb: \"{name}\"", file=metadata)
165
+ print("source: \"\"", file=metadata)
166
+ print("source_url: \"\"", file=metadata)
167
+ with inside('tests'):
168
+ with open(f'{name}.rs', 'a') as tests_rs:
169
+ print("// Add your tests here", file=tests_rs)
170
+
171
+
172
+ def make_exercise_with_specifications(name, exercise_dir, canonical_data, use_maplit):
173
+ print("Creating exercise from specification...")
174
+ # Update Cargo.toml
175
+ with open('Cargo.toml', 'r') as cargo_toml:
176
+ cargo_data = cargo_toml.read()
177
+ with open('Cargo.toml', 'w') as cargo_toml:
178
+ for line in cargo_data.splitlines():
179
+ if 'version' in canonical_data and line.lower().startswith('version'):
180
+ print(f"version = \"{canonical_data['version']}\"", file=cargo_toml)
181
+ elif line.lower().startswith('name'):
182
+ print(f"name = \"{to_crate_name(name)}\"", file=cargo_toml)
183
+ else:
184
+ print(line.strip(), file=cargo_toml)
185
+
186
+ tests_filename = os.path.join(exercise_dir, 'tests', f'{name}.rs')
187
+ # prepend doc comment about the nature of this file
188
+ with open(tests_filename, 'r') as tests_rs:
189
+ existing = tests_rs.read()
190
+ with open(tests_filename, 'w') as tests_rs:
191
+ print(f'//! Tests for {name}', file=tests_rs)
192
+ print('//!', file=tests_rs)
193
+ print('//! Generated by [script][script] using [canonical data][canonical-data]',
194
+ file=tests_rs)
195
+ print("//!", file=tests_rs)
196
+ print("//! [script]: https://github.com/exercism/rust/blob/master/bin/init_exercise.py",
197
+ file=tests_rs)
198
+ print("//! [canonical-data]: {}".format(url_for(name, 'canonical_data.json')),
199
+ file=tests_rs)
200
+ if 'comments' in canonical_data:
201
+ c = canonical_data['comments']
202
+ print('//!', file=tests_rs)
203
+ if isinstance(c, list) or isinstance(c, tuple):
204
+ for l in c:
205
+ print(f'//! {l}', file=tests_rs)
206
+ else:
207
+ print(f'//! {c}', file=tests_rs)
208
+
209
+ print(file=tests_rs)
210
+ print(file=tests_rs)
211
+ tests_rs.write(existing)
212
+
213
+ # now add test data
214
+ with open(tests_filename, 'a') as tests_rs:
215
+ first_case = True
216
+
217
+ # {property : {(input key names), ...}}
218
+ PIK_MAP = {}
219
+
220
+ def generate_pik_map(cases):
221
+ nonlocal PIK_MAP
222
+ for case in cases:
223
+ if 'property' in case:
224
+ ikeys = get_input_keys(case)
225
+ if ikeys is not None:
226
+ pkeys = PIK_MAP.get(case['property'], set())
227
+ pkeys.add(ikeys)
228
+ PIK_MAP[case['property']] = pkeys
229
+ if 'cases' in case:
230
+ generate_pik_map(case['cases'])
231
+
232
+ def collect_properties(cases):
233
+ properties = set()
234
+ for case in cases:
235
+ if 'expected' in case and 'property' in case:
236
+ properties.add(case['property'])
237
+ if 'cases' in case:
238
+ properties |= collect_properties(case['cases'])
239
+ return properties
240
+
241
+ def property_processor(property):
242
+ print(f"/// Process a single test case for the property `{property}`", file=tests_rs)
243
+ print("///", file=tests_rs)
244
+ print(f"/// All cases for the `{property}` property are implemented",
245
+ file=tests_rs)
246
+ print("/// in terms of this function.", file=tests_rs)
247
+ print('///', file=tests_rs)
248
+ print("/// Note that you'll need to both name the expected transform which",
249
+ file=tests_rs)
250
+ print("/// the student needs to write, and name the types of the inputs and outputs.",
251
+ file=tests_rs)
252
+ print("/// While rustc _may_ be able to handle things properly given a working example,",
253
+ file=tests_rs)
254
+ print("/// students will face confusing errors if the `I` and `O` types are not concrete.",
255
+ file=tests_rs)
256
+ if property in PIK_MAP:
257
+ print('///', file=tests_rs)
258
+ if len(PIK_MAP[property]) == 1:
259
+ print(f"/// Expected input format: {next(iter(PIK_MAP[property]))}",
260
+ file=tests_rs)
261
+ else:
262
+ print("/// CAUTION: Multiple input formats were detected in this test's cases:",
263
+ file=tests_rs)
264
+ for ifmt in PIK_MAP[property]:
265
+ print(f"/// {ifmt}")
266
+ print(
267
+ f"fn process_{property.lower()}_case<I, O>(input: I, expected: O) {{", file=tests_rs)
268
+ print(" // typical implementation:", file=tests_rs)
269
+ print(" // assert_eq!(", file=tests_rs)
270
+ print(f" // student_{property}_func(input),", file=tests_rs)
271
+ print(" // expected", file=tests_rs)
272
+ print(" // )", file=tests_rs)
273
+ print(" unimplemented!()", file=tests_rs)
274
+ print("}", file=tests_rs)
275
+ print(file=tests_rs)
276
+
277
+ def literal(item):
278
+ if isinstance(item, str):
279
+ return f'"{item}"'
280
+ elif isinstance(item, tuple):
281
+ return "({})".format(
282
+ ', '.join((literal(i) for i in item))
283
+ )
284
+ elif isinstance(item, list):
285
+ return "vec![{}]".format(
286
+ ', '.join((literal(i) for i in item))
287
+ )
288
+ elif isinstance(item, dict):
289
+ if use_maplit:
290
+ return "hashmap!{{{}}}".format(
291
+ ','.join((
292
+ "{}=>{}".format(literal(k), literal(v))
293
+ for k, v in item.items()
294
+ ))
295
+ )
296
+ else:
297
+ return "{{let mut hm=::std::collections::HashMap::new();{}hm}}".format(
298
+ ''.join((
299
+ "hm.insert({}, {});".format(literal(k), literal(v))
300
+ for k, v in item.items()
301
+ ))
302
+ )
303
+ else:
304
+ return str(item)
305
+
306
+ def write_case(case):
307
+ nonlocal first_case
308
+
309
+ print("#[test]", file=tests_rs)
310
+ if first_case:
311
+ first_case = False
312
+ else:
313
+ print("#[ignore]", file=tests_rs)
314
+ print(f"/// {case['description']}", file=tests_rs)
315
+ if 'comments' in case:
316
+ print('///', file=tests_rs)
317
+ if isinstance(case['comments'], list):
318
+ for line in case['comments']:
319
+ print(f"/// {line}", file=tests_rs)
320
+ else:
321
+ print(f"/// {case['comments']}", file=tests_rs)
322
+ print("fn test_{}() {{".format(to_item_name(case['description'])), file=tests_rs)
323
+ print(" process_{}_case({}, {});".format(
324
+ case['property'].lower(),
325
+ literal(case['input']),
326
+ literal(case['expected'])),
327
+ file=tests_rs)
328
+ print("}", file=tests_rs)
329
+ print(file=tests_rs)
330
+
331
+ def get_input_keys(item):
332
+ if 'description' in item and 'expected' in item:
333
+ return tuple(sorted(set(item.keys()) -
334
+ {'comments',
335
+ 'description',
336
+ 'expected',
337
+ 'property'}
338
+ ))
339
+ # else None
340
+
341
+ def write_cases(cases):
342
+ for item in cases:
343
+ if 'description' in item and 'expected' not in item:
344
+ if isinstance(item['description'], list):
345
+ for line in item['description']:
346
+ print(f"// {line}", file=tests_rs)
347
+ else:
348
+ print(f"// {item['description']}", file=tests_rs)
349
+ if 'comments' in item and 'expected' not in item:
350
+ if isinstance(item['comments'], list):
351
+ for line in item['comments']:
352
+ print(f"// {line}", file=tests_rs)
353
+ else:
354
+ print(f"// {item['comments']}", file=tests_rs)
355
+ if 'expected' not in item and 'comments' in item or 'description' in item:
356
+ print(file=tests_rs)
357
+ if 'property' not in item:
358
+ item['property'] = ''
359
+ if all(key in item for key in ('description', 'input', 'expected')):
360
+ write_case(item)
361
+ elif 'description' in item and 'expected' in item:
362
+ item['input'] = tuple((item[k] for k in get_input_keys(item)))
363
+ write_case(item)
364
+ if 'cases' in item:
365
+ write_cases(item['cases'])
366
+
367
+ generate_pik_map(canonical_data['cases'])
368
+
369
+ for ppty in collect_properties(canonical_data['cases']):
370
+ property_processor(ppty)
371
+
372
+ write_cases(canonical_data['cases'])
373
+
374
+
375
+ def prompt(prompt, validator):
376
+ """
377
+ Prompt the user for a value
378
+
379
+ Validator is a function which accepts the user's input and either
380
+ returns a (possibly transformed) value, or raises an exception.
381
+ On an exception, the user is asked again.
382
+ """
383
+ while True:
384
+ try:
385
+ return validator(input(prompt).strip())
386
+ except Exception as e:
387
+ print(f"Problem: {e}")
388
+
389
+
390
+ def update_config(name):
391
+ "Update the configuration based on user input"
392
+ with inside(REPO_ROOT):
393
+ with open('config.json') as config_json:
394
+ config = json.load(config_json, object_pairs_hook=collections.OrderedDict)
395
+
396
+ while True:
397
+ conf_values = collections.OrderedDict()
398
+ conf_values['uuid'] = str(uuid4())
399
+ conf_values['slug'] = name
400
+ conf_values['core'] = False
401
+
402
+ def unlock_validator(v):
403
+ if len(v) == 0:
404
+ return None
405
+ if not any(v == ex['slug'] for ex in config['exercises']):
406
+ raise ValueError(f"{v} is not an existing exercise slug")
407
+ return v
408
+ conf_values['unlocked_by'] = prompt(
409
+ "Exercise slug which unlocks this (blank for None): ", unlock_validator)
410
+
411
+ def difficulty_validator(v):
412
+ i = int(v)
413
+ if i <= 0 or i > 10:
414
+ raise ValueError("difficulty must be > 0 and <= 10")
415
+ return i
416
+ conf_values['difficulty'] = prompt(
417
+ "Difficulty for this exercise([1...10]): ", difficulty_validator)
418
+
419
+ def topics_validator(v):
420
+ topics = [t.strip() for t in v.split(',') if len(t.strip()) > 0]
421
+ if len(topics) == 0:
422
+ raise ValueError("must enter at least one topic")
423
+ return topics
424
+ conf_values['topics'] = prompt(
425
+ "List of topics for this exercise, comma-separated: ", topics_validator)
426
+
427
+ print("You have configured this exercise as follows:")
428
+ print(json.dumps(conf_values, sort_keys=True, indent=4))
429
+
430
+ yn = input('Is this correct? (y/N): ').strip().lower()
431
+ if len(yn) > 0 and yn[0] == 'y':
432
+ break
433
+
434
+ if not any(conf_values['difficulty'] == ex['difficulty'] for ex in config['exercises']):
435
+ config['exercises'].append(conf_values)
436
+ config['exercises'].sort(key=lambda ex: ex['difficulty'])
437
+ else:
438
+ # find the index bounds before which we might insert this
439
+ first_idx = None
440
+ last_idx = None
441
+ for idx, exercise in enumerate(config['exercises']):
442
+ if 'difficulty' in exercise and exercise['difficulty'] == conf_values['difficulty'] and first_idx is None:
443
+ first_idx = idx
444
+ if 'difficulty' in exercise and exercise['difficulty'] != conf_values['difficulty'] and first_idx is not None:
445
+ last_idx = idx
446
+ if last_idx is None:
447
+ last_idx = len(config['exercises'])
448
+
449
+ def binary_search(start_idx, end_idx):
450
+ if start_idx == end_idx:
451
+ return start_idx
452
+ mid_idx = start_idx + ((end_idx - start_idx) // 2)
453
+
454
+ def easy_hard_validator(v):
455
+ v = v.lower()[0]
456
+ if v not in {'e', 'h'}:
457
+ raise ValueError("must enter 'easier' or 'harder' or a substring")
458
+ return v
459
+ relative_difficulty = prompt(
460
+ f"Is {name} easier or harder than {config['exercises'][mid_idx]['slug']}: ",
461
+ easy_hard_validator
462
+ )
463
+
464
+ if relative_difficulty == 'e':
465
+ return binary_search(start_idx, mid_idx)
466
+ else:
467
+ return binary_search(mid_idx + 1, end_idx)
468
+
469
+ while True:
470
+ insert_idx = binary_search(first_idx, last_idx)
471
+ if insert_idx == 0:
472
+ ptext = f"{name} is the easiest exercise in the track."
473
+ elif insert_idx == len(config['exercises']):
474
+ ptext = f"{name} is the hardest exercise in the track."
475
+ else:
476
+ ptext = "{} fits between {} and {} in difficulty.".format(
477
+ name,
478
+ config['exercises'][insert_idx - 1]['slug'],
479
+ config['exercises'][insert_idx]['slug'],
480
+ )
481
+ print(f"You have indicated that {ptext}")
482
+ yn = input('Is this correct? (y/N): ').strip().lower()
483
+ if len(yn) > 0 and yn[0] == 'y':
484
+ break
485
+
486
+ config['exercises'].insert(insert_idx, conf_values)
487
+
488
+ with inside(REPO_ROOT):
489
+ with open('config.json', 'w') as config_json:
490
+ json.dump(
491
+ config,
492
+ config_json,
493
+ sort_keys=False,
494
+ indent=2,
495
+ )
496
+ # end the config file with a newline
497
+ print(file=config_json)
498
+
499
+
500
+ @contextmanager
501
+ def git_master(git_path):
502
+ "A context inside of which you are on the clean master branch"
503
+ with inside(git_path):
504
+ dirty = len(output_of('git status --porcelain')) > 0
505
+ if dirty:
506
+ subprocess.run(['git', 'stash'])
507
+ branch = output_of('git rev-parse --abbrev-ref HEAD')
508
+ if branch != 'master':
509
+ subprocess.run(['git', 'checkout', 'master'])
510
+ subprocess.run(['git', 'pull'])
511
+
512
+ try:
513
+ yield
514
+ finally:
515
+ if branch != 'master':
516
+ subprocess.run(['git', 'checkout', branch])
517
+ if dirty:
518
+ subprocess.run(['git', 'stash', 'pop'])
519
+
520
+
521
+ def generate_readme(exercise_name, get_problem_specification):
522
+ configlet = None
523
+ with inside(os.path.join(REPO_ROOT, 'bin')):
524
+ if not os.path.exists('configlet') and not os.path.exists('configlet.exe'):
525
+ with inside(REPO_ROOT):
526
+ subprocess.run(os.path.join('bin', 'fetch-configlet'))
527
+ for configlet_name in ('configlet', 'configlet.exe'):
528
+ if os.path.exists(configlet_name):
529
+ configlet = configlet_name
530
+ break
531
+ if configlet is None:
532
+ print("Could not locate configlet; aborting", file=sys.stderr)
533
+ sys.exit(1)
534
+ if get_problem_specification:
535
+ with inside(os.path.join(REPO_ROOT, '..')):
536
+ if os.path.exists('problem-specifications'):
537
+ with git_master('problem-specifications'):
538
+ with inside(REPO_ROOT):
539
+ subprocess.run([
540
+ os.path.join('bin', configlet),
541
+ 'generate', '.',
542
+ '--only', exercise_name,
543
+ '--spec-path',
544
+ os.path.join('..', 'problem-specifications')
545
+ ])
546
+ else:
547
+ subprocess.run(
548
+ ['git', 'clone', 'https://github.com/exercism/problem-specifications.git']
549
+ )
550
+ with inside(REPO_ROOT):
551
+ subprocess.run([
552
+ os.path.join('bin', configlet),
553
+ 'generate', '.',
554
+ '--only', exercise_name,
555
+ '--spec-path',
556
+ os.path.join('..', 'problem-specifications')
557
+ ])
558
+ else:
559
+ with inside(REPO_ROOT):
560
+ subprocess.run([
561
+ os.path.join('bin', configlet),
562
+ 'generate', '.',
563
+ '--only', exercise_name,
564
+ ])
565
+
566
+
567
+ if __name__ == '__main__':
568
+ import argparse
569
+ parser = argparse.ArgumentParser(description='Create a Rust Track exercise for Exercism')
570
+ parser.add_argument('name', help='name of the exercise to create')
571
+ parser.add_argument('--dont-create-exercise', action='store_true',
572
+ help='Don\'t create the exercise. Useful when just updating config.json')
573
+ parser.add_argument('--dont-update-config', action='store_true',
574
+ help='Don\'t update config.json. Useful when you don\'t yet '
575
+ 'have a sense of exercise difficulty.')
576
+ parser.add_argument('--version', action='version', version=VERSION)
577
+ parser.add_argument('--use-maplit', action='store_true',
578
+ help='Use the maplit crate to improve readability of tests with lots of map literals')
579
+
580
+ args = parser.parse_args()
581
+
582
+ if not args.dont_create_exercise:
583
+ make_exercise(args.name, args.use_maplit)
584
+
585
+ if not args.dont_update_config:
586
+ update_config(args.name)
@@ -53,8 +53,7 @@
53
53
  "core": false,
54
54
  "unlocked_by": null,
55
55
  "difficulty": 1,
56
- "topics": [
57
- ]
56
+ "topics": []
58
57
  },
59
58
  {
60
59
  "uuid": "38ef1802-2730-4f94-bafe-d2cd6b3e7f95",
@@ -131,7 +130,7 @@
131
130
  "difficulty": 1,
132
131
  "topics": [
133
132
  "option"
134
- ]
133
+ ]
135
134
  },
136
135
  {
137
136
  "uuid": "9f649818-0c82-4b79-b912-4d65b9f60e10",
@@ -688,8 +687,7 @@
688
687
  "core": false,
689
688
  "unlocked_by": null,
690
689
  "difficulty": 4,
691
- "topics": [
692
- ]
690
+ "topics": []
693
691
  },
694
692
  {
695
693
  "uuid": "0a33f3ac-cedd-4a40-a132-9d044b0e9977",
@@ -698,11 +696,11 @@
698
696
  "unlocked_by": null,
699
697
  "difficulty": 6,
700
698
  "topics": [
701
- "lifetimes",
702
- "struct",
703
- "string parsing",
704
- "enum",
705
- "traits"
699
+ "lifetimes",
700
+ "struct",
701
+ "string parsing",
702
+ "enum",
703
+ "traits"
706
704
  ]
707
705
  },
708
706
  {
@@ -760,6 +758,18 @@
760
758
  "self mut"
761
759
  ]
762
760
  },
761
+ {
762
+ "uuid": "a78ed17a-b2c0-485c-814b-e13ccd1f4153",
763
+ "slug": "book-store",
764
+ "core": false,
765
+ "unlocked_by": null,
766
+ "difficulty": 7,
767
+ "topics": [
768
+ "algorithms",
769
+ "groups",
770
+ "set theory"
771
+ ]
772
+ },
763
773
  {
764
774
  "uuid": "704aab91-b83a-4e64-8c21-fb0be5076289",
765
775
  "slug": "ocr-numbers",
@@ -0,0 +1,3 @@
1
+ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
2
+ # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
3
+ Cargo.lock
@@ -0,0 +1,7 @@
1
+ [package]
2
+ name = "book_store"
3
+ version = "1.0.1"
4
+ authors = ["Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>"]
5
+
6
+ [dependencies]
7
+ noisy_float = "0.1.2"
@@ -0,0 +1,6 @@
1
+ [package]
2
+ name = "book_store"
3
+ version = "1.0.1"
4
+ authors = ["Peter Goodspeed-Niklaus <peter.r.goodspeedniklaus@gmail.com>"]
5
+
6
+ [dependencies]