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.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/problem-specifications/exercises/all-your-base/canonical-data.json +20 -29
- data/problem-specifications/exercises/forth/canonical-data.json +52 -5
- data/problem-specifications/exercises/isbn-verifier/canonical-data.json +15 -15
- data/problem-specifications/exercises/protein-translation/canonical-data.json +153 -0
- data/problem-specifications/exercises/reverse-string/canonical-data.json +39 -0
- data/problem-specifications/exercises/reverse-string/description.md +5 -0
- data/problem-specifications/exercises/reverse-string/metadata.yml +5 -0
- data/tracks/c/config.json +42 -41
- data/tracks/csharp/config.json +13 -0
- data/tracks/csharp/exercises/Exercises.sln +6 -0
- data/tracks/csharp/exercises/isbn-verifier/Example.cs +27 -0
- data/tracks/csharp/exercises/isbn-verifier/IsbnVerifier.cs +9 -0
- data/tracks/csharp/exercises/isbn-verifier/IsbnVerifier.csproj +19 -0
- data/tracks/csharp/exercises/isbn-verifier/IsbnVerifierTest.cs +84 -0
- data/tracks/csharp/exercises/isbn-verifier/README.md +40 -0
- data/tracks/csharp/generators/Exercises/IsbnVerifier.cs +15 -0
- data/tracks/delphi/config.json +28 -28
- data/tracks/erlang/config.json +10 -0
- data/tracks/erlang/exercises/accumulate/rebar.config +1 -1
- data/tracks/erlang/exercises/accumulate/test/accumulate_tests.erl +9 -7
- data/tracks/erlang/exercises/all-your-base/rebar.config +1 -1
- data/tracks/erlang/exercises/all-your-base/test/all_your_base_tests.erl +26 -24
- data/tracks/erlang/exercises/allergies/rebar.config +1 -1
- data/tracks/erlang/exercises/allergies/test/allergies_tests.erl +19 -17
- data/tracks/erlang/exercises/anagram/rebar.config +1 -3
- data/tracks/erlang/exercises/anagram/test/anagram_tests.erl +14 -12
- data/tracks/erlang/exercises/atbash-cipher/rebar.config +1 -1
- data/tracks/erlang/exercises/atbash-cipher/test/atbash_cipher_tests.erl +7 -5
- data/tracks/erlang/exercises/bank-account/rebar.config +1 -1
- data/tracks/erlang/exercises/bank-account/test/bank_account_tests.erl +52 -51
- data/tracks/erlang/exercises/beer-song/rebar.config +30 -0
- data/tracks/erlang/exercises/beer-song/test/beer_song_tests.erl +11 -10
- data/tracks/erlang/exercises/bob/rebar.config +1 -1
- data/tracks/erlang/exercises/bob/test/bob_tests.erl +33 -35
- data/tracks/erlang/exercises/circular-buffer/rebar.config +1 -1
- data/tracks/erlang/exercises/circular-buffer/test/circular_buffer_tests.erl +33 -32
- data/tracks/erlang/exercises/clock/rebar.config +1 -1
- data/tracks/erlang/exercises/clock/test/clock_tests.erl +57 -50
- data/tracks/erlang/exercises/collatz-conjecture/rebar.config +1 -1
- data/tracks/erlang/exercises/collatz-conjecture/test/collatz_conjecture_tests.erl +11 -9
- data/tracks/erlang/exercises/complex-numbers/rebar.config +1 -1
- data/tracks/erlang/exercises/complex-numbers/test/complex_numbers_tests.erl +102 -76
- data/tracks/erlang/exercises/custom-set/rebar.config +1 -1
- data/tracks/erlang/exercises/custom-set/test/custom_set_tests.erl +100 -109
- data/tracks/erlang/exercises/difference-of-squares/rebar.config +1 -1
- data/tracks/erlang/exercises/difference-of-squares/test/difference_of_squares_tests.erl +8 -7
- data/tracks/erlang/exercises/etl/rebar.config +1 -1
- data/tracks/erlang/exercises/etl/test/etl_tests.erl +6 -5
- data/tracks/erlang/exercises/gigasecond/rebar.config +1 -1
- data/tracks/erlang/exercises/gigasecond/test/gigasecond_tests.erl +9 -8
- data/tracks/erlang/exercises/grade-school/rebar.config +1 -1
- data/tracks/erlang/exercises/grade-school/test/grade_school_tests.erl +27 -23
- data/tracks/erlang/exercises/grains/rebar.config +1 -1
- data/tracks/erlang/exercises/grains/test/grains_tests.erl +7 -6
- data/tracks/erlang/exercises/hamming/rebar.config +1 -1
- data/tracks/erlang/exercises/hamming/test/hamming_tests.erl +20 -23
- data/tracks/erlang/exercises/hello-world/rebar.config +1 -1
- data/tracks/erlang/exercises/hello-world/test/hello_world_tests.erl +6 -4
- data/tracks/erlang/exercises/isogram/rebar.config +1 -1
- data/tracks/erlang/exercises/isogram/test/isogram_tests.erl +14 -12
- data/tracks/erlang/exercises/largest-series-product/rebar.config +1 -1
- data/tracks/erlang/exercises/largest-series-product/test/largest_series_product_tests.erl +22 -21
- data/tracks/erlang/exercises/leap/rebar.config +1 -1
- data/tracks/erlang/exercises/leap/test/leap_tests.erl +8 -7
- data/tracks/erlang/exercises/luhn/rebar.config +1 -1
- data/tracks/erlang/exercises/luhn/test/luhn_tests.erl +18 -16
- data/tracks/erlang/exercises/meetup/rebar.config +1 -1
- data/tracks/erlang/exercises/meetup/test/meetup_tests.erl +96 -95
- data/tracks/erlang/exercises/nucleotide-count/rebar.config +1 -1
- data/tracks/erlang/exercises/nucleotide-count/test/nucleotide_count_tests.erl +12 -11
- data/tracks/erlang/exercises/pangram/README.md +60 -0
- data/tracks/erlang/exercises/{accumulate → pangram}/include/exercism.hrl +0 -0
- data/tracks/erlang/exercises/{beer-song/rebar.conf → pangram/rebar.config} +0 -0
- data/tracks/erlang/exercises/pangram/src/example.erl +11 -0
- data/tracks/erlang/exercises/pangram/src/pangram.app.src +9 -0
- data/tracks/erlang/exercises/pangram/src/pangram.erl +8 -0
- data/tracks/erlang/exercises/pangram/test/pangram_tests.erl +38 -0
- data/tracks/erlang/exercises/parallel-letter-frequency/rebar.config +1 -1
- data/tracks/erlang/exercises/parallel-letter-frequency/test/parallel_letter_frequency_tests.erl +7 -6
- data/tracks/erlang/exercises/phone-number/rebar.config +1 -1
- data/tracks/erlang/exercises/phone-number/test/phone_number_tests.erl +13 -12
- data/tracks/erlang/exercises/rna-transcription/rebar.config +1 -1
- data/tracks/erlang/exercises/rna-transcription/test/rna_transcription_tests.erl +13 -11
- data/tracks/erlang/exercises/robot-simulator/rebar.config +1 -1
- data/tracks/erlang/exercises/robot-simulator/test/robot_simulator_tests.erl +70 -69
- data/tracks/erlang/exercises/roman-numerals/rebar.config +1 -1
- data/tracks/erlang/exercises/roman-numerals/test/roman_numerals_tests.erl +6 -5
- data/tracks/erlang/exercises/rotational-cipher/rebar.config +1 -8
- data/tracks/erlang/exercises/rotational-cipher/test/rotational_cipher_tests.erl +29 -27
- data/tracks/erlang/exercises/scrabble-score/rebar.config +1 -1
- data/tracks/erlang/exercises/scrabble-score/test/scrabble_score_tests.erl +6 -5
- data/tracks/erlang/exercises/series/rebar.config +1 -1
- data/tracks/erlang/exercises/series/test/series_tests.erl +6 -5
- data/tracks/erlang/exercises/sieve/rebar.config +1 -1
- data/tracks/erlang/exercises/sieve/test/sieve_tests.erl +10 -8
- data/tracks/erlang/exercises/space-age/rebar.config +1 -1
- data/tracks/erlang/exercises/space-age/test/space_age_tests.erl +20 -19
- data/tracks/erlang/exercises/spiral-matrix/rebar.config +1 -1
- data/tracks/erlang/exercises/spiral-matrix/test/spiral_matrix_tests.erl +12 -10
- data/tracks/erlang/exercises/strain/rebar.config +1 -1
- data/tracks/erlang/exercises/strain/test/strain_tests.erl +17 -16
- data/tracks/erlang/exercises/sum-of-multiples/rebar.config +1 -1
- data/tracks/erlang/exercises/sum-of-multiples/test/sum_of_multiples_tests.erl +16 -15
- data/tracks/erlang/exercises/triangle/rebar.config +1 -1
- data/tracks/erlang/exercises/triangle/test/triangle_tests.erl +20 -19
- data/tracks/erlang/exercises/two-fer/rebar.config +1 -1
- data/tracks/erlang/exercises/two-fer/test/two_fer_tests.erl +8 -7
- data/tracks/erlang/exercises/word-count/rebar.config +1 -1
- data/tracks/erlang/exercises/word-count/test/word_count_tests.erl +6 -5
- data/tracks/erlang/exercises/zipper/rebar.config +1 -1
- data/tracks/erlang/exercises/zipper/test/zipper_tests.erl +47 -46
- data/tracks/erlang/testgen/src/tgen.erl +8 -6
- data/tracks/erlang/testgen/src/tgen_bob.erl +1 -1
- data/tracks/erlang/testgen/src/tgen_collatz-conjecture.erl +2 -2
- data/tracks/erlang/testgen/src/tgen_complex-numbers.erl +11 -11
- data/tracks/erlang/testgen/src/tgen_custom-set.erl +14 -14
- data/tracks/erlang/testgen/src/tgen_hamming.erl +2 -2
- data/tracks/erlang/testgen/src/tgen_hello-world.erl +1 -1
- data/tracks/erlang/testgen/src/tgen_leap.erl +1 -1
- data/tracks/erlang/testgen/src/tgen_rna-transcription.erl +2 -2
- data/tracks/erlang/testgen/src/tgs.erl +6 -0
- data/tracks/fsharp/exercises/beer-song/BeerSong.fs +1 -5
- data/tracks/fsharp/exercises/beer-song/BeerSongTest.fs +342 -20
- data/tracks/fsharp/exercises/beer-song/Example.fs +16 -10
- data/tracks/fsharp/exercises/food-chain/Example.fs +1 -1
- data/tracks/fsharp/exercises/food-chain/FoodChain.fs +1 -3
- data/tracks/fsharp/exercises/food-chain/FoodChainTest.fs +11 -11
- data/tracks/fsharp/exercises/house/Example.fs +1 -1
- data/tracks/fsharp/exercises/house/House.fs +1 -3
- data/tracks/fsharp/exercises/house/HouseTest.fs +202 -202
- data/tracks/fsharp/exercises/proverb/Example.fs +9 -9
- data/tracks/fsharp/exercises/proverb/Proverb.fs +1 -3
- data/tracks/fsharp/exercises/proverb/ProverbTest.fs +50 -21
- data/tracks/fsharp/exercises/twelve-days/Example.fs +4 -4
- data/tracks/fsharp/exercises/twelve-days/TwelveDays.fs +2 -6
- data/tracks/fsharp/exercises/twelve-days/TwelveDaysTest.fs +81 -57
- data/tracks/fsharp/generators/Generators.fs +39 -15
- data/tracks/go/bin/run-generators +5 -1
- data/tracks/go/config.json +14 -2
- data/tracks/go/exercises/accumulate/example.go +2 -1
- data/tracks/go/exercises/zebra-puzzle/.meta/hints.md +24 -0
- data/tracks/go/exercises/zebra-puzzle/README.md +76 -0
- data/tracks/go/exercises/zebra-puzzle/example.go +256 -0
- data/tracks/go/exercises/zebra-puzzle/zebra_puzzle_test.go +18 -0
- data/tracks/haskell/exercises/forth/package.yaml +1 -1
- data/tracks/haskell/exercises/forth/test/Tests.hs +1 -4
- data/tracks/haskell/exercises/pascals-triangle/package.yaml +1 -1
- data/tracks/haskell/exercises/pascals-triangle/test/Tests.hs +22 -1
- data/tracks/java/POLICIES.md +3 -3
- data/tracks/java/config.json +21 -2
- data/tracks/java/exercises/atbash-cipher/.meta/src/reference/java/Atbash.java +12 -12
- data/tracks/java/exercises/protein-translation/.meta/src/reference/java/ProteinTranslator.java +47 -0
- data/tracks/java/exercises/protein-translation/.meta/version +1 -0
- data/tracks/java/exercises/protein-translation/README.md +58 -0
- data/tracks/java/exercises/protein-translation/build.gradle +18 -0
- data/tracks/java/exercises/protein-translation/src/main/java/ProteinTranslator.java +8 -0
- data/tracks/java/exercises/protein-translation/src/test/java/ProteinTranslatorTest.java +179 -0
- data/tracks/java/exercises/settings.gradle +1 -0
- data/tracks/javascript/.eslintignore +2 -2
- data/tracks/javascript/exercises/accumulate/example.js +1 -2
- data/tracks/javascript/exercises/grains/example.js +6 -3
- data/tracks/kotlin/exercises/beer-song/.meta/src/reference/kotlin/BeerSong.kt +3 -3
- data/tracks/kotlin/exercises/beer-song/.meta/version +1 -1
- data/tracks/kotlin/exercises/beer-song/README.md +1 -1
- data/tracks/kotlin/exercises/beer-song/src/test/kotlin/BeerSongTest.kt +6 -6
- data/tracks/kotlin/exercises/forth/.meta/version +1 -1
- data/tracks/kotlin/exercises/forth/src/test/kotlin/ForthEvaluatorTest.kt +0 -7
- data/tracks/kotlin/exercises/meetup/README.md +16 -12
- data/tracks/kotlin/exercises/nth-prime/.meta/src/reference/kotlin/Prime.kt +7 -2
- data/tracks/kotlin/exercises/nth-prime/.meta/version +1 -1
- data/tracks/kotlin/exercises/nth-prime/src/test/kotlin/PrimeTest.kt +10 -1
- data/tracks/kotlin/exercises/nucleotide-count/README.md +2 -2
- data/tracks/kotlin/exercises/pascals-triangle/.meta/src/reference/kotlin/PascalsTriangle.kt +1 -1
- data/tracks/kotlin/exercises/pascals-triangle/.meta/version +1 -1
- data/tracks/kotlin/exercises/pascals-triangle/src/test/kotlin/PascalsTriangleTest.kt +58 -1
- data/tracks/kotlin/exercises/sum-of-multiples/README.md +3 -3
- data/tracks/ocaml/exercises/forth/test.ml +1 -3
- data/tracks/ocaml/exercises/rectangles/example.ml +11 -11
- data/tracks/php/exercises/bob/example.php +2 -2
- data/tracks/purescript/config.json +3 -3
- data/tracks/python/config.json +34 -22
- data/tracks/python/exercises/alphametics/example.py +90 -34
- data/tracks/python/exercises/ocr-numbers/example.py +18 -21
- data/tracks/python/exercises/ocr-numbers/ocr_numbers.py +1 -5
- data/tracks/python/exercises/ocr-numbers/ocr_numbers_test.py +124 -106
- data/tracks/python/exercises/simple-linked-list/README.md +49 -0
- data/tracks/python/exercises/simple-linked-list/example.py +67 -0
- data/tracks/python/exercises/simple-linked-list/hints.md +10 -0
- data/tracks/python/exercises/simple-linked-list/simple_linked_list.py +33 -0
- data/tracks/python/exercises/simple-linked-list/simple_linked_list_test.py +112 -0
- data/tracks/rust/README.md +2 -0
- data/tracks/rust/bin/init_exercise.py +586 -0
- data/tracks/rust/config.json +20 -10
- data/tracks/rust/exercises/book-store/.gitignore +3 -0
- data/tracks/rust/exercises/book-store/Cargo-example.toml +7 -0
- data/tracks/rust/exercises/book-store/Cargo.toml +6 -0
- data/tracks/rust/exercises/book-store/README.md +107 -0
- data/tracks/rust/exercises/book-store/example.rs +187 -0
- data/tracks/rust/exercises/book-store/src/lib.rs +3 -0
- data/tracks/rust/exercises/book-store/tests/book-store.rs +130 -0
- data/tracks/sml/config.json +8 -8
- data/tracks/typescript/config.json +6 -6
- metadata +43 -46
- data/tracks/erlang/exercises/all-your-base/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/allergies/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/anagram/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/atbash-cipher/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/bank-account/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/beer-song/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/bob/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/circular-buffer/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/clock/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/collatz-conjecture/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/complex-numbers/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/custom-set/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/difference-of-squares/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/etl/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/gigasecond/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/grade-school/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/grains/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/hamming/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/hello-world/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/isogram/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/largest-series-product/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/leap/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/luhn/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/meetup/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/nucleotide-count/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/parallel-letter-frequency/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/phone-number/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/rna-transcription/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/robot-simulator/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/roman-numerals/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/rotational-cipher/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/scrabble-score/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/series/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/sieve/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/space-age/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/spiral-matrix/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/strain/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/sum-of-multiples/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/triangle/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/two-fer/include/exercism.hrl +0 -11
- data/tracks/erlang/exercises/word-count/include/exercism.hrl +0 -11
- 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)
|
data/tracks/rust/config.json
CHANGED
@@ -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
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
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",
|