-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathlearning-objectives.js
114 lines (101 loc) · 3.66 KB
/
learning-objectives.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
110
111
112
113
114
import yaml from 'js-yaml';
import { readFile } from 'node:fs/promises';
import { flattenLearningObjectives } from './project.js';
export const loadYaml = async fname => (
yaml.load(await readFile(fname, 'utf8'))
);
// returns { es: { tree, flat }, pt: { tree, flat } }
const learningObjectivesByLanguage = async (langs, dir) => {
const localized = await Promise.all(langs.map(async lang => loadYaml(`${dir}/intl/${lang}.yml`)));
return localized.reduce((accum, objectives, i) => Object.assign(accum, {
[langs[i]]: {
tree: objectives,
flat: flattenLearningObjectives(objectives),
},
}), {});
};
// uses objectives defined in data.yml and compares them
// to the objectives in the intl files
// returns { missing: { objective: [lang] }, noTitle: { objective: [lang] } }
const findMissingObjectives = (definedObjectives, intlObjectives) => (
definedObjectives.reduce((accum, defined) => {
const { missingIntl, noTitle } = accum;
Object.keys(intlObjectives).forEach((lang) => {
const { tree, flat } = intlObjectives[lang];
if (!flat.find(o => o === defined)) {
if (missingIntl[defined]) {
missingIntl[defined].push(lang);
} else {
missingIntl[defined] = [lang];
}
} else if (!tree[defined]?.title) {
if (noTitle[defined]) {
noTitle[defined].push(lang);
} else {
noTitle[defined] = [lang];
}
}
});
return { missingIntl, noTitle };
}, { missingIntl: {}, noTitle: {} })
);
export const parseLearningObjectives = async (dir, opts = {}) => {
const tree = await loadYaml(`${dir}/data.yml`);
const flat = flattenLearningObjectives(tree);
const langs = ['es', 'pt'];
const intl = await learningObjectivesByLanguage(langs, dir);
if (opts.validate) {
const invalidatedObjectives = findMissingObjectives(flat, intl);
const { missingIntl, noTitle } = invalidatedObjectives;
const noTitleObjectives = Object.keys(noTitle);
// if we are strictly checking, we only want objectives missing in all langs
const missingIntlObjectives = opts.strict
? Object.keys(missingIntl).filter(obj => missingIntl[obj].length === langs.length)
: Object.keys(missingIntl);
if (missingIntlObjectives.length || noTitleObjectives.length) {
const messageOutput = [];
messageOutput.push('Found the following learning objectives with problems:');
messageOutput.push(
`==> ${missingIntlObjectives.length} learning objectives missing from ${opts.strict
? 'all' : 'at least one'} intl yml:`,
);
const intlErrors = missingIntlObjectives.reduce((acc, objective) => {
acc.push(`* ${objective} missing in langs: ${missingIntl[objective]}`);
return acc;
}, []);
messageOutput.push(
...intlErrors,
'------------',
);
messageOutput.push(
`==> ${noTitleObjectives.length} learning objectives without title:`,
);
const titleErrors = noTitleObjectives.reduce((acc, objective) => {
acc.push(`* ${objective} has no title in langs: ${noTitle[objective]}`);
return acc;
}, []);
messageOutput.push(
...titleErrors,
'------------',
);
throw Object.assign(
new Error(messageOutput.join('\n')),
{ path: dir },
);
}
}
return {
tree,
flat,
intl: Object.keys(intl).reduce((acc, lang) => {
acc[lang] = intl[lang].tree;
return acc;
}, {}),
table: flat.map(key => ({
key,
...Object.keys(intl).reduce((accum, lang) => Object.assign(accum, {
[lang]: intl[lang].tree[key]?.title || intl[lang].tree[key],
}), {}),
})),
};
};