Skip to content

Commit 53d07b0

Browse files
initial setup with Hello World exercise (#3)
* initial setup and Hello World exercise * setup CI stage * sync docs and config * basic test generator * remove skipping of tests for now
1 parent bd25c37 commit 53d07b0

24 files changed

+3549
-44
lines changed

.github/workflows/test.yml

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,20 @@ on:
1313

1414
jobs:
1515
verify_exercises:
16-
# TODO: replace with another image if required to run the tests (optional)
1716
runs-on: ubuntu-24.04
1817

1918
steps:
2019
- name: Checkout repository
2120
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
2221

23-
# TODO: setup any tooling that is required to run the tests (optional)
24-
# E.g. install a specific version of a programming language
25-
# E.g. install packages via apt/apk/yum/etc.
26-
# Find GitHub Actions to setup tooling here:
27-
# - https://github.com/actions/?q=setup&type=&language=
28-
# - https://github.com/actions/starter-workflows/tree/main/ci
29-
# - https://github.com/marketplace?type=actions&query=setup
30-
# - name: Use <setup tooling>
31-
# uses: <action to setup tooling>
22+
- name: Setup Node
23+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
24+
with:
25+
node-version: 24.x
26+
cache: "npm"
3227

33-
# TODO: install any dependencies (optional)
34-
# E.g. npm install, bundle install, etc.
35-
# - name: Install project dependencies
36-
# run: <install dependencies>
28+
- name: Install dependencies
29+
run: npm ci
3730

3831
- name: Verify all exercises
3932
run: bin/verify-exercises

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ bin/configlet.exe
55
bin/latest-configlet.tar.gz
66
bin/latest-configlet.zip
77
bin/configlet.zip
8+
node_modules
9+
10+
tmp
11+
exercises/**/.gitignore
12+
lib

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "problem-specifications"]
2+
path = problem-specifications
3+
url = https://github.com/exercism/problem-specifications.git

Makefile

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
.DEFAULT_GOAL := test
2+
3+
EXERCISE ?= ""
4+
EXERCISES = $(shell find ./exercises/practice -maxdepth 1 -mindepth 1 -type d | cut -s -d '/' -f4 | sort)
5+
OUTDIR ?= "tmp"
6+
7+
# check all package.json and package-lock.json are matching
8+
check-package-files:
9+
@echo "Validation package.json files..."
10+
@for pkg in $(PKG_FILES); do \
11+
! ./bin/md5-hash $$pkg | grep -qv $(SOURCE_PKG_MD5) || { echo "$$pkg does not match main package.json. Please run 'make sync-package-files' locally and commit the results."; exit 1; }; \
12+
done
13+
@echo "Validation package-lock.json files..."
14+
@for pkg in $(PKG_LOCK_FILES); do \
15+
! ./bin/md5-hash $$pkg | grep -qv $(SOURCE_PKG_LOCK_MD5) || { echo "$$pkg does not match main package.json. Please run 'make sync-package-files' locally and commit the results."; exit 1; }; \
16+
done
17+
@echo "package-file check complete..."
18+
19+
# copy package.json and package-lock.json for single exercise
20+
copy-package-file:
21+
@cp package.json exercises/practice/$(EXERCISE)/package.json
22+
@cp package-lock.json exercises/practice/$(EXERCISE)/package-lock.json
23+
@cp rescript.json exercises/practice/$(EXERCISE)/rescript.json
24+
25+
# copy package files to all exercise directories
26+
sync-package-files:
27+
@echo "Syncing package.json and package-lock.json..."
28+
@for exercise in $(EXERCISES); do EXERCISE=$$exercise $(MAKE) -s copy-package-file || exit 1; done
29+
30+
copy-exercise:
31+
if [ -f exercises/practice/$(EXERCISE)/src/*.res ]; then \
32+
echo "Copying $(EXERCISE)"; \
33+
cp exercises/practice/$(EXERCISE)/.meta/*.res $(OUTDIR)/src/; \
34+
cp exercises/practice/$(EXERCISE)/tests/*.res $(OUTDIR)/tests/; \
35+
fi
36+
37+
copy-all-exercises:
38+
@echo "Copying exercises for testing..."
39+
@mkdir -p $(OUTDIR)/src
40+
@mkdir -p $(OUTDIR)/tests
41+
@for exercise in $(EXERCISES); do EXERCISE=$$exercise $(MAKE) -s copy-exercise || exit 1; done
42+
43+
# Remove the OUTDIR
44+
clean:
45+
@echo "Cleaning tmp directory..."
46+
@rm -rf $(OUTDIR)
47+
48+
# Format all ReScript files in the project
49+
format:
50+
@echo "Formatting ReScript files..."
51+
@find . -name "node_modules" -prune -o -name "*.res" -print -o -name "*.resi" -print | xargs npx rescript format
52+
53+
generate-tests:
54+
@echo "Generating tests for all exercises..."
55+
@for exercise in $(EXERCISES); do \
56+
if [ -f exercises/practice/$$exercise/.meta/generateTests.js ]; then \
57+
echo "-> Generating: $$exercise"; \
58+
node exercises/practice/$$exercise/.meta/generateTests.js || exit 1; \
59+
else \
60+
echo "-> Skipping: $$exercise (no generator found)"; \
61+
fi \
62+
done
63+
@echo "All tests generated successfully."
64+
65+
generate-test:
66+
ifeq ($(EXERCISE),"")
67+
$(error EXERCISE variable is required. usage: make generate_test EXERCISE=hello-world)
68+
endif
69+
@node exercises/practice/$(EXERCISE)/.meta/generateTests.js
70+
71+
test:
72+
$(MAKE) -s clean
73+
$(MAKE) -s check-package-files
74+
$(MAKE) -s copy-all-exercises
75+
npm run ci

bin/verify-exercises

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ required_tool() {
2121

2222
required_tool jq
2323

24-
copy_example_or_examplar_to_solution() {
24+
copy_example_or_exemplar_to_solution() {
2525
jq -c '[.files.solution, .files.exemplar // .files.example] | transpose | map({src: .[1], dst: .[0]}) | .[]' .meta/config.json \
2626
| while read -r src_and_dst; do
2727
cp "$(jq -r '.src' <<< "${src_and_dst}")" "$(jq -r '.dst' <<< "${src_and_dst}")"
@@ -30,45 +30,37 @@ copy_example_or_examplar_to_solution() {
3030

3131
unskip_tests() {
3232
jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
33-
noop # TODO: replace this with the command to unskip the tests.
34-
# Note: this function runs from within an exercise directory.
35-
# Note: the exercise directory is a temporary directory, so feel
36-
# free to modify its (test) files as needed.
37-
# Note: ignore this function if either:
38-
# - skipping tests is not supported, or
39-
# - skipping tests does not require modifying the test files.
40-
# Example: sed -i 's/test.skip/test/g' "${test_file}"
33+
sed -i 's/skip(/test(/g' "${test_file}" # replace skip utility function name with test
4134
done
4235
}
4336

4437
run_tests() {
45-
noop # TODO: replace this with the command to run the tests for the exercise.
46-
# Note: this function runs from within an exercise directory.
47-
# Note: the exercise directory is a temporary directory, so feel
48-
# free to modify its files as needed.
49-
# Note: return a zero exit code if all tests pass, otherwise non-zero.
50-
# Example: `npm test`
51-
# Example: `python3 -m pytest two_fer_test.py`
38+
node_modules/.bin/rescript
39+
node_modules/.bin/retest tests/*.js
5240
}
5341

5442
verify_exercise() {
5543
local dir
5644
local slug
5745
local tmp_dir
46+
local root_dir
5847

48+
root_dir=$(realpath .) # capture project root before cd-ing away
5949
dir=$(realpath "${1}")
6050
slug=$(basename "${dir}")
6151
tmp_dir=$(mktemp -d -t "exercism-verify-${slug}-XXXXX")
6252

6353
echo "Verifying ${slug} exercise..."
6454

6555
(
66-
trap 'rm -rf "$tmp_dir"' EXIT # remove tempdir when subshell ends
56+
trap 'rm -rf "$tmp_dir"' EXIT
6757
cp -r "${dir}/." "${tmp_dir}"
6858
cd "${tmp_dir}"
6959

70-
copy_example_or_examplar_to_solution
71-
unskip_tests
60+
ln -s "${root_dir}/node_modules" "${tmp_dir}/node_modules"
61+
62+
copy_example_or_exemplar_to_solution
63+
# unskip_tests
7264
run_tests
7365
)
7466
}

config.json

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,87 @@
88
"representer": false,
99
"analyzer": false
1010
},
11-
"blurb": "TODO: add blurb",
11+
"blurb": "ReScript is a strongly typed functional language which compiles to both Javascript and native.",
1212
"version": 3,
1313
"online_editor": {
1414
"indent_style": "space",
15-
"indent_size": 4
15+
"indent_size": 2,
16+
"highlightjs_language": "reasonml"
17+
},
18+
"test_runner": {
19+
"average_run_time": 7
1620
},
1721
"files": {
18-
"solution": [],
19-
"test": [],
20-
"example": [],
21-
"exemplar": []
22+
"solution": [
23+
"src/%{pascal_slug}.res"
24+
],
25+
"test": [
26+
"tests/%{pascal_slug}_test.res"
27+
],
28+
"example": [
29+
".meta/%{pascal_slug}.res"
30+
],
31+
"exemplar": [
32+
".meta/src/%{pascal_slug}.res"
33+
],
34+
"editor": [
35+
"src/%{pascal_slug}.resi"
36+
]
2237
},
2338
"exercises": {
24-
"concept": [],
25-
"practice": []
39+
"practice": [
40+
{
41+
"slug": "hello-world",
42+
"name": "Hello World",
43+
"uuid": "63d72b81-c7a4-4a94-8224-69f4c68cc374",
44+
"practices": [],
45+
"prerequisites": [],
46+
"difficulty": 1
47+
}
48+
]
2649
},
27-
"concepts": [],
28-
"key_features": [],
29-
"tags": []
50+
"key_features": [
51+
{
52+
"title": "OCaml's type system",
53+
"content": "ReScript brings OCaml's battle-tested powerful type system to JavaScript.",
54+
"icon": "stable"
55+
},
56+
{
57+
"title": "Inferred types",
58+
"content": "Types are inferred by the ReScript compiler and are guaranteed to be correct.",
59+
"icon": "immutable"
60+
},
61+
{
62+
"title": "Functional Programming",
63+
"content": "Like OCaml, ReScript is a functional programming language with pattern matching, variants and more.",
64+
"icon": "functional"
65+
},
66+
{
67+
"title": "JavaScript interop",
68+
"content": "JavaScript interoperability is easy, allowing existing JavaScript packages to be used.",
69+
"icon": "interop"
70+
},
71+
{
72+
"title": "Fast compiler",
73+
"content": "ReScript compilation times are super fast which means fast iteration cycles.",
74+
"icon": "fast"
75+
},
76+
{
77+
"title": "Refactor with ease",
78+
"content": "Compiler guides you through all places that need to be fixed during refactor until it just works.",
79+
"icon": "fun"
80+
}
81+
],
82+
"tags": [
83+
"execution_mode/compiled",
84+
"paradigm/functional",
85+
"platform/linux",
86+
"platform/mac",
87+
"platform/web",
88+
"platform/windows",
89+
"typing/static",
90+
"typing/strong",
91+
"used_for/frontends",
92+
"used_for/web_development"
93+
]
3094
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Instructions
2+
3+
The classical introductory exercise.
4+
Just say "Hello, World!".
5+
6+
["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment.
7+
8+
The objectives are simple:
9+
10+
- Modify the provided code so that it produces the string "Hello, World!".
11+
- Run the test suite and make sure that it succeeds.
12+
- Submit your solution and check it at the website.
13+
14+
If everything goes well, you will be ready to fetch your first real exercise.
15+
16+
[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let hello = () => "Hello, World!"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"authors": [
3+
"tejasbubane",
4+
"therealowenrees"
5+
],
6+
"contributors": [
7+
"naartjie",
8+
"ohana54",
9+
"son1112",
10+
"stevejb71"
11+
],
12+
"files": {
13+
"solution": [
14+
"src/HelloWorld.res"
15+
],
16+
"test": [
17+
"tests/HelloWorld_test.res"
18+
],
19+
"example": [
20+
".meta/HelloWorld.res"
21+
],
22+
"editor": [
23+
"src/HelloWorld.resi"
24+
]
25+
},
26+
"blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".",
27+
"source": "This is an exercise to introduce users to using Exercism",
28+
"source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program"
29+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { fileURLToPath } from 'node:url';
2+
import path from 'node:path';
3+
import getValidCases from '../../../../test_generator/getCases.js';
4+
import { generate, toPascalCase } from '../../../../test_generator/testGenerator.js';
5+
import * as template from './testTemplate.js';
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
const cases = getValidCases(template.slug);
9+
const outputPath = path.resolve(__dirname, '..', 'tests', `${toPascalCase(template.slug)}_test.res`);
10+
11+
generate(outputPath, template.slug, cases, template);

0 commit comments

Comments
 (0)