-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
109 lines (92 loc) · 3.01 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/*
Accept an Exercism track configuration object and output a track that looks
sorta like this:
...
├── binary-search [5]
| └── binary-search-tree [5]
|
├── kindergarten-garden [3]
| ├── tournament [3]
| | ├── poker [5]
| | | ├── bracket-push [7]
| | | | └── forth [8]
| | | └── ocr-numbers [7]
| └── change [5]
...
*/
const EOL = require('os').EOL,
INDENT = 3,
sortByDifficulty = (a, b) => a.difficulty > b.difficulty;
module.exports = function exercismConfigToTree(config){
let exercises = [...config.exercises];
let slugToExercise = {}, // global lookup table for exercises via slug
coreExercises = [],
bonusExercises = [];
exercises = exercises.map(e => {
// make sure any exercise can have child unlocks, and that all exercises
// are registered with the global lookup table, and that they have
// a description we can use in the tree
let {slug, difficulty} = e;
e.children = [];
slugToExercise[slug] = e
e.description = `${slug} [${difficulty}]`;
return e;
// then filter out the deprecated exercises
}).filter(e => e.hasOwnProperty('deprecated') ? !e.deprecated : true);
// if core is set it is a core exercise
// if not core and unlocked_by is something it is a child exercise
// else it is floating which is considered bonus
for(let e of exercises){
if(e.core){
coreExercises.push(e);
continue;
}
let parent = e.hasOwnProperty('unlocked_by') ? e.unlocked_by : false;
if(parent){
slugToExercise[parent].children.push(e);
}else{
bonusExercises.push(e);
}
}
// recursively create lines
function exercisesToTreeLines(exercises, depth=0){
let lines = [],
trunk = '|',
spacing = ' '.repeat(INDENT),
branch = '─'.repeat(INDENT - 1),
lastIndex = exercises.length - 1;
for(let i = 0; i < exercises.length; i++){
let line = '',
{description, children} = exercises[i],
fork = '├', // assume another line below...
last = (i === lastIndex);
// ...unless there is not
if(last && (children.length === 0)) fork = '└';
// create the continuing vertical line for each descent
for(let d = 0; d < depth; d++) {
line += trunk;
line += spacing;
}
line += `${fork}${branch} ${description}`;
lines.push(line);
children.sort(sortByDifficulty);
lines = lines.concat(...exercisesToTreeLines(children, depth + 1));
// add a little more separation between the top level exercises
if((!last) && (depth === 0)) lines.push(trunk);
}
return lines;
}
// add some spacing and titles
let lines = [];
lines.push('');
lines.push('core');
lines.push('----');
lines.push(...exercisesToTreeLines(coreExercises));
lines.push('');
lines.push('bonus');
lines.push('-----');
lines.push('');
lines.push(...exercisesToTreeLines(bonusExercises));
lines.push('');
return lines.join(EOL);
}