trackler 2.2.1.62 → 2.2.1.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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]