Skip to content

Commit e4f5c19

Browse files
authored
Merge pull request #48 from coderoad/feature/markdown-as-master
Feature/markdown as master
2 parents b34375b + aa745f9 commit e4f5c19

18 files changed

+551
-316
lines changed

Diff for: README.md

+10-14
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ The configuration file is created by matching the `level` and `step` ids between
4848

4949
Tutorial description.
5050

51-
## L1 This is a level with id = 1
51+
## 1. This is a level with id = 1
5252

5353
This level has two steps...
5454

55-
### L1S1 First step
55+
### 1.1 First step
5656

5757
The first step with id L1S1. The Step id should start with the level id.
5858

@@ -62,7 +62,7 @@ The first step with id L1S1. The Step id should start with the level id.
6262
- The second hint that will show
6363
- The third and final hint, as it is last in order
6464

65-
### L1S2 The second step
65+
### 1.2 The second step
6666

6767
The second step...
6868
```
@@ -72,10 +72,10 @@ The second step...
7272
```yaml
7373
---
7474
levels:
75-
- id: L1
75+
- id: "1"
7676
config: {}
7777
steps:
78-
- id: L1S1
78+
- id: "1.1"
7979
setup:
8080
files:
8181
- package.json
@@ -86,7 +86,7 @@ levels:
8686
- package.json
8787
commands:
8888
- npm install
89-
- id: L1S2
89+
- id: "1.2"
9090
setup:
9191
files:
9292
- src/server.js
@@ -104,23 +104,19 @@ commit 8e0e3a42ae565050181fdb68298114df21467a74 (HEAD -> v2, origin/v2)
104104
Author: creator <[email protected]>
105105
Date: Sun May 3 16:16:01 2020 -0700
106106

107-
L1S1Q setup step 1 for level 1
107+
1.1 setup for level 1, step 1
108108

109109
commit 9499611fc9b311040dcabaf2d98439fc0c356cc9
110110
Author: creator <[email protected]>
111111
Date: Sun May 3 16:13:37 2020 -0700
112112

113-
L1S2A checkout solution for level 1, step 2
113+
1.1S solution for level 1, step 1
114114

115115
commit c5c62041282579b495d3589b2eb1fdda2bcd7155
116116
Author: creator <[email protected]>
117117
Date: Sun May 3 16:11:42 2020 -0700
118118

119-
L1S2Q setup level 1, step 2
119+
1.2 setup for level 1, step 2
120120
```
121121
122-
Note that the step `L1S2` has two commits, one with the suffix `Q` and another one with `A`. The suffixes mean `Question` and `Answer`, respectively, and refer to the unit tests and the commit that makes them pass.
123-
124-
Steps defined as questions are **required** as they are meant to set the task to be executed by the student. The answer is optional and should be used when a commit must be loaded to verify the student's solution.
125-
126-
If there are multiple commits for a level or step, they are captured in order.
122+
Note that the step `1.1` has two commits, one with the suffix `S`. The first commit refers to the required tests and setup, while the second optional commit contains the solution. If there are multiple commits for a level or step, they are captured in order.

Diff for: package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coderoad/cli",
3-
"version": "0.3.1",
3+
"version": "0.4.0",
44
"description": "A CLI to build the configuration file for Coderoad Tutorials",
55
"keywords": [
66
"coderoad",

Diff for: src/build.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async function build(args: string[]) {
102102
try {
103103
const valid = validateSchema(skeletonSchema, skeleton);
104104
if (!valid) {
105-
console.error("Tutorial validation failed. See above to see what to fix");
105+
console.error("Skeleton validation failed. See above to see what to fix");
106106
return;
107107
}
108108
} catch (e) {

Diff for: src/schema/meta.ts

+35
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,40 @@ export default {
9595
},
9696
additionalProperties: false,
9797
},
98+
setup_action_without_commits: {
99+
type: "object",
100+
description:
101+
"A collection of files/commands that run when a level/step or solution is loaded",
102+
properties: {
103+
files: {
104+
$ref: "#/definitions/file_array",
105+
},
106+
commands: {
107+
$ref: "#/definitions/command_array",
108+
},
109+
watchers: {
110+
type: "array",
111+
items: {
112+
$ref: "#/definitions/file_path",
113+
// uniqueItems: true,
114+
},
115+
description:
116+
"An array file paths that, when updated, will trigger the test runner to run",
117+
},
118+
filter: {
119+
type: "string",
120+
description:
121+
"A regex pattern that will be passed to the test runner to limit the number of tests running",
122+
examples: ["^TestSuiteName"],
123+
},
124+
subtasks: {
125+
type: "boolean",
126+
description:
127+
'A feature that shows subtasks: all active test names and the status of the tests (pass/fail). Use together with "filter"',
128+
examples: [true],
129+
},
130+
},
131+
additionalProperties: false,
132+
},
98133
},
99134
};

Diff for: src/schema/skeleton.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export default {
5353
examples: ["coderoad"],
5454
},
5555
setup: {
56-
$ref: "#/definitions/setup_action",
56+
$ref: "#/definitions/setup_action_without_commits",
5757
description:
58-
"Setup commits or commands used for setting up the test runner on tutorial launch",
58+
"Setup actions or commands used for setting up the test runner on tutorial launch",
5959
},
6060
},
6161
required: ["command", "args"],
@@ -135,9 +135,9 @@ export default {
135135
examples: ["L1", "L11"],
136136
},
137137
setup: {
138-
$ref: "#/definitions/setup_action",
138+
$ref: "#/definitions/setup_action_without_commits",
139139
description:
140-
"An optional point for loading commits, running commands or opening files",
140+
"An optional point for running actions, commands or opening files",
141141
},
142142
steps: {
143143
type: "array",
@@ -152,18 +152,18 @@ export default {
152152
setup: {
153153
allOf: [
154154
{
155-
$ref: "#/definitions/setup_action",
155+
$ref: "#/definitions/setup_action_without_commits",
156156
description:
157-
"A point for loading commits. It can also run commands and/or open files",
157+
"A point for running actions, commands and/or opening files",
158158
},
159159
],
160160
},
161161
solution: {
162162
allOf: [
163163
{
164-
$ref: "#/definitions/setup_action",
164+
$ref: "#/definitions/setup_action_without_commits",
165165
description:
166-
"The solution commits that can be loaded if the user gets stuck. It can also run commands and/or open files",
166+
"The solution can be loaded if the user gets stuck. It can run actions, commands and/or open files",
167167
},
168168
{
169169
required: [],

Diff for: src/schema/tutorial.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ export default {
211211
examples: ["Have you tried doing X?"],
212212
},
213213
},
214-
required: ["content", "setup", "solution"],
214+
required: ["content", "setup"],
215215
},
216216
},
217217
},

Diff for: src/utils/commits.ts

+53-38
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from "fs";
22
import util from "util";
33
import * as path from "path";
4+
import { ListLogSummary } from "simple-git/typings/response";
45
import gitP, { SimpleGit } from "simple-git/promise";
56
import { validateCommitOrder } from "./validateCommits";
67

@@ -15,6 +16,54 @@ type GetCommitOptions = {
1516

1617
export type CommitLogObject = { [position: string]: string[] };
1718

19+
20+
21+
export function parseCommits(logs: ListLogSummary<any>): { [hash: string]: string[]} {
22+
// Filter relevant logs
23+
const commits: CommitLogObject = {};
24+
const positions: string[] = [];
25+
26+
for (const commit of logs.all) {
27+
const matches = commit.message.match(
28+
/^(?<init>INIT)|(L?(?<levelId>\d+)[S|\.]?(?<stepId>\d+)?(?<stepType>[Q|A|T|S])?)/
29+
);
30+
31+
if (matches && matches.length) {
32+
// Use an object of commit arrays to collect all commits
33+
const { groups } = matches
34+
let position
35+
if (groups.init) {
36+
position = 'INIT'
37+
} else if (groups.levelId && groups.stepId) {
38+
let stepType
39+
// @deprecated Q
40+
if (!groups.stepType || ['Q', 'T'].includes(groups.stepType)) {
41+
stepType = 'T' // test
42+
// @deprecated A
43+
} else if (!groups.stepType || ['A', 'S'].includes(groups.stepType)) {
44+
stepType = 'S' // solution
45+
}
46+
position = `${groups.levelId}.${groups.stepId}:${stepType}`
47+
} else if (groups.levelId) {
48+
position = groups.levelId
49+
} else {
50+
console.warn(`No matcher for commit "${commit.message}"`)
51+
}
52+
commits[position] = [...(commits[position] || []), commit.hash]
53+
positions.unshift(position);
54+
} else {
55+
const initMatches = commit.message.match(/^INIT/);
56+
if (initMatches && initMatches.length) {
57+
commits.INIT = [...(commits.INIT || []), commit.hash]
58+
positions.unshift("INIT");
59+
}
60+
}
61+
}
62+
// validate order
63+
validateCommitOrder(positions);
64+
return commits;
65+
}
66+
1867
export async function getCommits({
1968
localDir,
2069
codeBranch,
@@ -49,48 +98,16 @@ export async function getCommits({
4998
// track the original branch in case of failure
5099
const originalBranch = branches.current;
51100

52-
// Filter relevant logs
53-
const commits: CommitLogObject = {};
54-
55101
try {
56102
// Checkout the code branches
57103
await git.checkout(codeBranch);
58104

59105
// Load all logs
60106
const logs = await git.log();
61-
const positions: string[] = [];
62-
63-
for (const commit of logs.all) {
64-
const matches = commit.message.match(
65-
/^(?<stepId>(?<levelId>L\d+)(S\d+))(?<stepType>[QA])?/
66-
);
67-
68-
if (matches && matches.length) {
69-
// Use an object of commit arrays to collect all commits
70-
const position = matches[0];
71-
if (!commits[position]) {
72-
// does not exist, create the list
73-
commits[position] = [commit.hash];
74-
} else {
75-
// add to the list
76-
commits[position].push(commit.hash);
77-
}
78-
positions.unshift(position);
79-
} else {
80-
const initMatches = commit.message.match(/^INIT/);
81-
if (initMatches && initMatches.length) {
82-
if (!commits.INIT) {
83-
// does not exist, create the list
84-
commits.INIT = [commit.hash];
85-
} else {
86-
// add to the list
87-
commits.INIT.push(commit.hash);
88-
}
89-
positions.unshift("INIT");
90-
}
91-
}
92-
}
93-
validateCommitOrder(positions);
107+
108+
const commits = parseCommits(logs);
109+
110+
return commits;
94111
} catch (e) {
95112
console.error("Error with checkout or commit matching");
96113
throw new Error(e.message);
@@ -100,6 +117,4 @@ export async function getCommits({
100117
// cleanup the tmp directory
101118
await rmdir(tmpDir, { recursive: true });
102119
}
103-
104-
return commits;
105120
}

0 commit comments

Comments
 (0)