@learnpack/learnpack 5.0.33 → 5.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -12
- package/lib/commands/breakToken.js +36 -8
- package/lib/commands/init.js +49 -37
- package/lib/managers/config/exercise.js +16 -0
- package/lib/managers/server/routes.js +21 -0
- package/lib/models/exercise-obj.d.ts +1 -0
- package/lib/utils/creatorUtilities.d.ts +43 -1
- package/lib/utils/creatorUtilities.js +123 -22
- package/lib/utils/rigoActions.d.ts +3 -1
- package/lib/utils/rigoActions.js +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +4 -1
- package/src/commands/breakToken.ts +67 -36
- package/src/commands/init.ts +100 -65
- package/src/managers/config/exercise.ts +18 -1
- package/src/managers/server/routes.ts +26 -0
- package/src/models/exercise-obj.ts +30 -29
- package/src/utils/creatorUtilities.ts +143 -23
- package/src/utils/rigoActions.ts +3 -1
package/README.md
CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
|
|
21
21
|
$ learnpack COMMAND
|
22
22
|
running command...
|
23
23
|
$ learnpack (-v|--version|version)
|
24
|
-
@learnpack/learnpack/5.0.
|
24
|
+
@learnpack/learnpack/5.0.35 win32-x64 node-v22.14.0
|
25
25
|
$ learnpack --help [COMMAND]
|
26
26
|
USAGE
|
27
27
|
$ learnpack COMMAND
|
@@ -76,7 +76,7 @@ DESCRIPTION
|
|
76
76
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
77
77
|
```
|
78
78
|
|
79
|
-
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
79
|
+
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\audit.ts)_
|
80
80
|
|
81
81
|
## `learnpack breakToken`
|
82
82
|
|
@@ -91,7 +91,7 @@ OPTIONS
|
|
91
91
|
-y, --yes Skip all prompts and initialize an empty project
|
92
92
|
```
|
93
93
|
|
94
|
-
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
94
|
+
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\breakToken.ts)_
|
95
95
|
|
96
96
|
## `learnpack clean`
|
97
97
|
|
@@ -106,7 +106,7 @@ DESCRIPTION
|
|
106
106
|
Extra documentation goes here
|
107
107
|
```
|
108
108
|
|
109
|
-
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
109
|
+
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\clean.ts)_
|
110
110
|
|
111
111
|
## `learnpack download [PACKAGE]`
|
112
112
|
|
@@ -124,7 +124,7 @@ DESCRIPTION
|
|
124
124
|
Extra documentation goes here
|
125
125
|
```
|
126
126
|
|
127
|
-
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
127
|
+
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\download.ts)_
|
128
128
|
|
129
129
|
## `learnpack help [COMMAND]`
|
130
130
|
|
@@ -156,7 +156,7 @@ OPTIONS
|
|
156
156
|
-y, --yes Skip all prompts and initialize an empty project
|
157
157
|
```
|
158
158
|
|
159
|
-
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
159
|
+
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\init.ts)_
|
160
160
|
|
161
161
|
## `learnpack login [PACKAGE]`
|
162
162
|
|
@@ -174,7 +174,7 @@ DESCRIPTION
|
|
174
174
|
Extra documentation goes here
|
175
175
|
```
|
176
176
|
|
177
|
-
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
177
|
+
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\login.ts)_
|
178
178
|
|
179
179
|
## `learnpack logout [PACKAGE]`
|
180
180
|
|
@@ -192,7 +192,7 @@ DESCRIPTION
|
|
192
192
|
Extra documentation goes here
|
193
193
|
```
|
194
194
|
|
195
|
-
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
195
|
+
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\logout.ts)_
|
196
196
|
|
197
197
|
## `learnpack plugins`
|
198
198
|
|
@@ -323,7 +323,7 @@ OPTIONS
|
|
323
323
|
-h, --help show CLI help
|
324
324
|
```
|
325
325
|
|
326
|
-
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
326
|
+
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\publish.ts)_
|
327
327
|
|
328
328
|
## `learnpack start`
|
329
329
|
|
@@ -345,7 +345,7 @@ OPTIONS
|
|
345
345
|
-y, --yes Skip all prompts and initialize an empty project
|
346
346
|
```
|
347
347
|
|
348
|
-
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
348
|
+
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\start.ts)_
|
349
349
|
|
350
350
|
## `learnpack test [EXERCISESLUG]`
|
351
351
|
|
@@ -362,7 +362,7 @@ OPTIONS
|
|
362
362
|
-y, --yes Skip all prompts and initialize an empty project
|
363
363
|
```
|
364
364
|
|
365
|
-
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
365
|
+
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\test.ts)_
|
366
366
|
|
367
367
|
## `learnpack translate`
|
368
368
|
|
@@ -376,7 +376,7 @@ OPTIONS
|
|
376
376
|
-y, --yes Skip all prompts and initialize an empty project
|
377
377
|
```
|
378
378
|
|
379
|
-
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
379
|
+
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\translate.ts)_
|
380
380
|
<!-- commandsstop -->
|
381
381
|
|
382
382
|
> > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
|
@@ -4,18 +4,46 @@ const command_1 = require("@oclif/command");
|
|
4
4
|
const BaseCommand_1 = require("../utils/BaseCommand");
|
5
5
|
const console_1 = require("../utils/console");
|
6
6
|
const creatorUtilities_1 = require("../utils/creatorUtilities");
|
7
|
+
const exampleMd = `# How to Install Node.js
|
8
|
+
|
9
|
+
Node.js lets you run JavaScript outside a web browser.
|
10
|
+
|
11
|
+
## Step 1: Download Node.js
|
12
|
+
|
13
|
+
Get the Node.js installer from the [official site](https://nodejs.org/en/download/).
|
14
|
+
|
15
|
+
## Step 2: Install Node.js
|
16
|
+
|
17
|
+
Open the installer and follow the steps to finish.
|
18
|
+
|
19
|
+
## Step 3: Verify the Installation
|
20
|
+
|
21
|
+
Open a terminal and type:
|
22
|
+
|
23
|
+
\`\`\`bash
|
24
|
+
node -v
|
25
|
+
\`\`\`
|
26
|
+
`;
|
7
27
|
class BreakTokenCommand extends BaseCommand_1.default {
|
8
28
|
async run() {
|
9
29
|
const { flags } = this.parse(BreakTokenCommand);
|
10
30
|
// await SessionManager.breakToken()
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
31
|
+
const paragraphs = (0, creatorUtilities_1.extractParagraphs)(exampleMd);
|
32
|
+
for (const paragraph of paragraphs) {
|
33
|
+
const syllables = (0, creatorUtilities_1.splitIntoSyllables)(paragraph);
|
34
|
+
const words = (0, creatorUtilities_1.extractWords)(paragraph);
|
35
|
+
const sentences = (0, creatorUtilities_1.countSentences)(paragraph);
|
36
|
+
const fkgl = (0, creatorUtilities_1.fleschKincaidGrade)(paragraph);
|
37
|
+
console_1.default.info(paragraph);
|
38
|
+
console_1.default.info(`Number of syllables: ${syllables.length}`);
|
39
|
+
console_1.default.info(syllables);
|
40
|
+
console_1.default.info(`Number of words: ${words.length}`);
|
41
|
+
console_1.default.info(words);
|
42
|
+
console_1.default.info(`Number of sentences: ${sentences}`);
|
43
|
+
console_1.default.info(`FKGL: ${fkgl}`);
|
44
|
+
console_1.default.info("---");
|
45
|
+
}
|
46
|
+
process.exit(0);
|
19
47
|
}
|
20
48
|
}
|
21
49
|
BreakTokenCommand.description = "Break the token";
|
package/lib/commands/init.js
CHANGED
@@ -20,6 +20,12 @@ const durationByKind = {
|
|
20
20
|
quiz: 2,
|
21
21
|
read: 1,
|
22
22
|
};
|
23
|
+
const PARAMS = {
|
24
|
+
expected_grade_level: "6",
|
25
|
+
max_fkgl: 8,
|
26
|
+
max_words: 200,
|
27
|
+
max_rewrite_attempts: 3,
|
28
|
+
};
|
23
29
|
const whichTargetAudience = async () => {
|
24
30
|
const res = await prompts([
|
25
31
|
{
|
@@ -30,6 +36,48 @@ const whichTargetAudience = async () => {
|
|
30
36
|
]);
|
31
37
|
return res.targetAudience;
|
32
38
|
};
|
39
|
+
async function processExercise(rigoToken, steps, packageContext, exercise, exercisesDir) {
|
40
|
+
const { exNumber, exTitle, kind, description } = (0, creatorUtilities_1.getExInfo)(exercise);
|
41
|
+
const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`);
|
42
|
+
const readme = await (0, rigoActions_1.readmeCreator)(rigoToken, {
|
43
|
+
title: `${exNumber} - ${exTitle}`,
|
44
|
+
output_lang: "en",
|
45
|
+
list_of_exercises: steps.join(","),
|
46
|
+
tutorial_description: packageContext,
|
47
|
+
lesson_description: description,
|
48
|
+
kind: kind.toLowerCase(),
|
49
|
+
});
|
50
|
+
const duration = durationByKind[kind.toLowerCase()];
|
51
|
+
let attempts = 0;
|
52
|
+
let readability = (0, creatorUtilities_1.checkReadability)(readme.parsed.content, 200, duration || 1);
|
53
|
+
while (readability.fkglResult.fkgl > PARAMS.max_fkgl &&
|
54
|
+
attempts < PARAMS.max_rewrite_attempts) {
|
55
|
+
console_1.default.warning(`The lesson ${exTitle} has as readability score of ${readability.fkglResult.fkgl} . It exceeds the maximum of words per minute. Rewriting it... (Attempt ${attempts + 1})`);
|
56
|
+
// eslint-disable-next-line
|
57
|
+
const reducedReadme = await (0, rigoActions_1.makeReadmeReadable)(rigoToken, {
|
58
|
+
lesson: readability.body,
|
59
|
+
number_of_words: readability.minutes.toString(),
|
60
|
+
expected_number_words: PARAMS.max_words.toString(),
|
61
|
+
fkgl_results: JSON.stringify(readability.fkglResult),
|
62
|
+
expected_grade_level: PARAMS.expected_grade_level,
|
63
|
+
});
|
64
|
+
if (!reducedReadme)
|
65
|
+
break;
|
66
|
+
readability = (0, creatorUtilities_1.checkReadability)(reducedReadme.parsed.content, PARAMS.max_words, duration || 1);
|
67
|
+
attempts++;
|
68
|
+
}
|
69
|
+
console_1.default.success(`After ${attempts} attempts, the lesson ${exTitle} has a readability score of ${readability.fkglResult.fkgl} using FKGL. And it has ${readability.minutes.toFixed(2)} minutes of reading time.`);
|
70
|
+
const readmeFilename = "README.md";
|
71
|
+
fs.writeFileSync(path.join(exerciseDir, readmeFilename), readability.newMarkdown);
|
72
|
+
if (kind.toLowerCase() === "code") {
|
73
|
+
const codeFile = await (0, rigoActions_1.createCodeFile)(rigoToken, {
|
74
|
+
readme: readability.newMarkdown,
|
75
|
+
tutorial_info: packageContext,
|
76
|
+
});
|
77
|
+
fs.writeFileSync(path.join(exerciseDir, `app.${codeFile.parsed.extension.replace(".", "")}`), codeFile.parsed.content);
|
78
|
+
}
|
79
|
+
return readability.newMarkdown;
|
80
|
+
}
|
33
81
|
const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
|
34
82
|
let prevInteractions = "";
|
35
83
|
let isReady = false;
|
@@ -180,43 +228,7 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
|
|
180
228
|
const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`);
|
181
229
|
fs.ensureDirSync(exerciseDir);
|
182
230
|
}
|
183
|
-
const exercisePromises = steps.map(
|
184
|
-
const { exNumber, exTitle, kind, description } = (0, creatorUtilities_1.getExInfo)(exercise);
|
185
|
-
const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`);
|
186
|
-
const readme = await (0, rigoActions_1.readmeCreator)(rigoToken, {
|
187
|
-
title: `${exNumber} - ${exTitle}`,
|
188
|
-
output_lang: "en",
|
189
|
-
list_of_exercises: steps.join(","),
|
190
|
-
tutorial_description: packageContext,
|
191
|
-
lesson_description: description,
|
192
|
-
kind: kind.toLowerCase(),
|
193
|
-
});
|
194
|
-
const duration = durationByKind[kind.toLowerCase()];
|
195
|
-
let readingTime = (0, creatorUtilities_1.checkReadingTime)(readme.parsed.content, 200, duration || 1);
|
196
|
-
if (readingTime.exceedsThreshold) {
|
197
|
-
// Console.info(
|
198
|
-
// `The reading time for the lesson ${exTitle} exceeds the threshold, reducing it...`
|
199
|
-
// )
|
200
|
-
const reducedReadme = await (0, rigoActions_1.reduceReadme)(rigoToken, {
|
201
|
-
lesson: readingTime.body,
|
202
|
-
number_of_words: readingTime.minutes.toString(),
|
203
|
-
expected_number_words: "200",
|
204
|
-
});
|
205
|
-
if (reducedReadme) {
|
206
|
-
readingTime = (0, creatorUtilities_1.checkReadingTime)(reducedReadme.parsed.content, 200, duration || 1);
|
207
|
-
}
|
208
|
-
}
|
209
|
-
const readmeFilename = "README.md";
|
210
|
-
fs.writeFileSync(path.join(exerciseDir, readmeFilename), readingTime.newMarkdown);
|
211
|
-
if (kind.toLowerCase() === "code") {
|
212
|
-
const codeFile = await (0, rigoActions_1.createCodeFile)(rigoToken, {
|
213
|
-
readme: readme.parsed.content,
|
214
|
-
tutorial_info: packageContext,
|
215
|
-
});
|
216
|
-
fs.writeFileSync(path.join(exerciseDir, `app.${codeFile.parsed.extension.replace(".", "")}`), codeFile.parsed.content);
|
217
|
-
}
|
218
|
-
return readingTime.newMarkdown;
|
219
|
-
});
|
231
|
+
const exercisePromises = steps.map(exercise => processExercise(rigoToken, steps, packageContext, exercise, exercisesDir));
|
220
232
|
const readmeContents = await Promise.all(exercisePromises);
|
221
233
|
console_1.default.success("Lessons created! 🎉");
|
222
234
|
console_1.default.info("Generating images for the lessons...");
|
@@ -94,6 +94,22 @@ const exercise = (path, position, configObject) => {
|
|
94
94
|
}
|
95
95
|
return content;
|
96
96
|
},
|
97
|
+
renameFolder: function (newName) {
|
98
|
+
if (!(config === null || config === void 0 ? void 0 : config.dirPath)) {
|
99
|
+
throw new Error("No config directory found");
|
100
|
+
}
|
101
|
+
try {
|
102
|
+
const newPath = p.join(config === null || config === void 0 ? void 0 : config.exercisesPath, newName);
|
103
|
+
fs.renameSync(this.path, newPath);
|
104
|
+
this.path = newPath;
|
105
|
+
this.slug = newName;
|
106
|
+
this.title = newName;
|
107
|
+
}
|
108
|
+
catch (error) {
|
109
|
+
console.log(error);
|
110
|
+
throw new Error("Failed to rename exercise: " + error);
|
111
|
+
}
|
112
|
+
},
|
97
113
|
saveFile: function (name, content) {
|
98
114
|
const file = this.files.find((f) => f.name === name);
|
99
115
|
if (file) {
|
@@ -312,6 +312,27 @@ async function default_1(app, configObject, configManager) {
|
|
312
312
|
return res.status(400).json({ error: error.message });
|
313
313
|
}
|
314
314
|
}));
|
315
|
+
app.put("/actions/rename", jsonBodyParser, withHandler(async (req, res) => {
|
316
|
+
const { slug, newSlug } = req.body;
|
317
|
+
const exercise = configManager.getExercise(slug);
|
318
|
+
if (!exercise) {
|
319
|
+
return res.status(400).json({ error: "Exercise not found" });
|
320
|
+
}
|
321
|
+
try {
|
322
|
+
if (exercise.renameFolder) {
|
323
|
+
exercise.renameFolder(newSlug);
|
324
|
+
res.json({ status: "ok" });
|
325
|
+
}
|
326
|
+
else {
|
327
|
+
res.status(500).json({
|
328
|
+
error: "Failed to rename exercise because it's not supported by the exercise",
|
329
|
+
});
|
330
|
+
}
|
331
|
+
}
|
332
|
+
catch (_a) {
|
333
|
+
res.status(500).json({ error: "Failed to rename exercise" });
|
334
|
+
}
|
335
|
+
}));
|
315
336
|
app.post("/exercise/:slug/create", jsonBodyParser, withHandler(async (req, res) => {
|
316
337
|
const { title, readme, language } = req.body;
|
317
338
|
const { slug } = req.params;
|
@@ -17,6 +17,7 @@ export interface IExercise {
|
|
17
17
|
getReadme?: (lang: string | null) => any;
|
18
18
|
getFile?: (name: string) => string | Buffer;
|
19
19
|
saveFile?: (name: string, content: string) => void;
|
20
|
+
renameFolder?: (newName: string) => void;
|
20
21
|
getTestReport?: () => any;
|
21
22
|
test?: (sessionConfig: any, config: IConfig, socket: ISocket) => void;
|
22
23
|
}
|
@@ -14,11 +14,16 @@ export type PackageInfo = {
|
|
14
14
|
us: string;
|
15
15
|
};
|
16
16
|
};
|
17
|
-
|
17
|
+
type TFKGLResult = {
|
18
|
+
text: string;
|
19
|
+
fkgl: number;
|
20
|
+
};
|
21
|
+
export declare function checkReadability(markdown: string, wordsPerMinute?: number, maxMinutes?: number): {
|
18
22
|
newMarkdown: string;
|
19
23
|
exceedsThreshold: boolean;
|
20
24
|
minutes: number;
|
21
25
|
body: string;
|
26
|
+
fkglResult: TFKGLResult;
|
22
27
|
};
|
23
28
|
export declare const getExInfo: (title: string) => {
|
24
29
|
exNumber: string;
|
@@ -48,4 +53,41 @@ export declare function createFileOnDesktop(): Promise<void>;
|
|
48
53
|
export declare function getContentIndex(): string;
|
49
54
|
export declare function extractTextFromMarkdown(mdContent: string): string;
|
50
55
|
export declare const saveTranslatedReadme: (exercise: string, languageCode: string, readme: string) => Promise<void>;
|
56
|
+
/**
|
57
|
+
* Extracts paragraphs from a Markdown string.
|
58
|
+
* @param markdownText The input Markdown string.
|
59
|
+
* @returns An array of paragraph contents.
|
60
|
+
*/
|
61
|
+
export declare function extractParagraphs(markdownText: string): string[];
|
62
|
+
/**
|
63
|
+
* Splits a paragraph into words and separates each word into syllables.
|
64
|
+
* @param paragraph The input paragraph.
|
65
|
+
* @returns An array of words, each split into syllables.
|
66
|
+
*/
|
67
|
+
export declare function splitIntoSyllables(paragraph: string): string[];
|
68
|
+
/**
|
69
|
+
* Splits a word into its syllables using a basic estimation.
|
70
|
+
* @param word The word to split.
|
71
|
+
* @returns An array of syllables.
|
72
|
+
*/
|
73
|
+
export declare function splitWordIntoSyllables(word: string): string[];
|
74
|
+
/**
|
75
|
+
* Extracts words from a given paragraph.
|
76
|
+
* @param paragraph The input text.
|
77
|
+
* @returns An array of words.
|
78
|
+
*/
|
79
|
+
export declare function extractWords(paragraph: string): string[];
|
80
|
+
/**
|
81
|
+
* Calculates the Flesch-Kincaid Grade Level (FKGL) for a given text.
|
82
|
+
* @param text The input paragraph.
|
83
|
+
* @returns The FKGL score.
|
84
|
+
*/
|
85
|
+
export declare function fleschKincaidGrade(text: string): TFKGLResult;
|
86
|
+
/**
|
87
|
+
* Counts the number of sentences in a given text.
|
88
|
+
* @param text The input paragraph.
|
89
|
+
* @returns The total number of sentences.
|
90
|
+
*/
|
91
|
+
export declare function countSentences(text: string): number;
|
92
|
+
export declare function howManyDifficultParagraphs(paragraphs: TFKGLResult[], maxFKGL: number): number;
|
51
93
|
export {};
|
@@ -1,14 +1,24 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.saveTranslatedReadme = exports.makePackageInfo = exports.getExInfo = exports.estimateReadingTime = void 0;
|
4
|
-
exports.
|
4
|
+
exports.checkReadability = checkReadability;
|
5
5
|
exports.extractImagesFromMarkdown = extractImagesFromMarkdown;
|
6
6
|
exports.getFilenameFromUrl = getFilenameFromUrl;
|
7
7
|
exports.estimateDuration = estimateDuration;
|
8
8
|
exports.createFileOnDesktop = createFileOnDesktop;
|
9
9
|
exports.getContentIndex = getContentIndex;
|
10
10
|
exports.extractTextFromMarkdown = extractTextFromMarkdown;
|
11
|
+
exports.extractParagraphs = extractParagraphs;
|
12
|
+
exports.splitIntoSyllables = splitIntoSyllables;
|
13
|
+
exports.splitWordIntoSyllables = splitWordIntoSyllables;
|
14
|
+
exports.extractWords = extractWords;
|
15
|
+
exports.fleschKincaidGrade = fleschKincaidGrade;
|
16
|
+
exports.countSentences = countSentences;
|
17
|
+
exports.howManyDifficultParagraphs = howManyDifficultParagraphs;
|
18
|
+
const console_1 = require("./console");
|
11
19
|
const frontMatter = require("front-matter");
|
20
|
+
const MarkdownIt = require("markdown-it");
|
21
|
+
const syllable_1 = require("syllable");
|
12
22
|
const path = require("path");
|
13
23
|
const fs = require("fs");
|
14
24
|
const os_1 = require("os");
|
@@ -38,36 +48,38 @@ const estimateReadingTime = (text, wordsPerMinute = 150) => {
|
|
38
48
|
};
|
39
49
|
};
|
40
50
|
exports.estimateReadingTime = estimateReadingTime;
|
41
|
-
function
|
51
|
+
function checkReadability(markdown, wordsPerMinute = 200, maxMinutes = 1) {
|
42
52
|
const parsed = frontMatter(markdown);
|
53
|
+
const fkglResult = fleschKincaidGrade(parsed.body);
|
43
54
|
const readingTime = (0, exports.estimateReadingTime)(parsed.body, wordsPerMinute);
|
44
55
|
// const readingEase = estimateReadingEase(parsed.body)
|
45
56
|
let attributes = parsed.attributes ? parsed.attributes : {};
|
46
57
|
if (typeof parsed.attributes !== "object") {
|
47
58
|
attributes = {};
|
48
59
|
}
|
49
|
-
const updatedAttributes = Object.assign(Object.assign({}, attributes), { readingTime });
|
60
|
+
const updatedAttributes = Object.assign(Object.assign({}, attributes), { readingTime, fkglResult: fkglResult.fkgl });
|
50
61
|
let yamlFrontMatter = "";
|
51
62
|
try {
|
52
63
|
yamlFrontMatter = yaml.dump(updatedAttributes).trim();
|
53
64
|
}
|
54
65
|
catch (_a) {
|
66
|
+
console_1.default.error("Error dumping YAML front matter");
|
55
67
|
return {
|
56
68
|
newMarkdown: "",
|
57
69
|
exceedsThreshold: false,
|
58
70
|
minutes: 0,
|
59
71
|
body: "",
|
72
|
+
fkglResult,
|
60
73
|
// readingEase: 0,
|
61
74
|
};
|
62
75
|
}
|
63
|
-
// Reconstruct the markdown with the front matter
|
64
76
|
const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`;
|
65
77
|
return {
|
66
78
|
newMarkdown,
|
67
79
|
exceedsThreshold: readingTime.minutes > maxMinutes,
|
68
80
|
minutes: readingTime.minutes,
|
69
81
|
body: parsed.body,
|
70
|
-
|
82
|
+
fkglResult,
|
71
83
|
};
|
72
84
|
}
|
73
85
|
const slugify = (text) => {
|
@@ -205,24 +217,16 @@ function getContentIndex() {
|
|
205
217
|
// return Math.round(206.835 - 1.015 * ASL - 84.6 * ASW)
|
206
218
|
// }
|
207
219
|
function extractTextFromMarkdown(mdContent) {
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
220
|
+
return mdContent
|
221
|
+
.replace(/!\[.*?]\(.*?\)/g, "") // Remove images
|
222
|
+
.replace(/\[.*?]\(.*?\)/g, "") // Remove links
|
223
|
+
.replace(/(```[\S\s]*?```|`.*?`)/g, "") // Remove inline & block code
|
224
|
+
.replace(/^#.*$/gm, "") // Remove headings
|
225
|
+
.replace(/(\*{1,2}|_{1,2})/g, "") // Remove bold/italic markers
|
226
|
+
.replace(/>\s?/g, "") // Remove blockquotes
|
227
|
+
.replace(/[*-]\s+/g, "") // Remove bullets from lists
|
228
|
+
.trim();
|
213
229
|
}
|
214
|
-
// export const estimateReadingEase = async (text: string): Promise<number> => {
|
215
|
-
// const cleanedText = extractTextFromMarkdown(text)
|
216
|
-
// // @ts-ignore
|
217
|
-
// const rs = (await import("text-readability")).default;
|
218
|
-
// // return fleschKincaidReadingEase(cleanedText)
|
219
|
-
// // const score = readability(cleanedText)
|
220
|
-
// // console.log(score)
|
221
|
-
// // return score.fleschKincaid ?? 0
|
222
|
-
// const score = rs.fleschReadingEase(cleanedText)
|
223
|
-
// console.log(score, "SCORE FLESCH READING EASE")
|
224
|
-
// return score
|
225
|
-
// }
|
226
230
|
const cleanReadme = (readme) => {
|
227
231
|
// Replace <text> and </text> with nothing
|
228
232
|
return readme.replace(/<text>/g, "").replace(/<\/text>/g, "");
|
@@ -232,3 +236,100 @@ const saveTranslatedReadme = async (exercise, languageCode, readme) => {
|
|
232
236
|
fs.writeFileSync(readmePath, cleanReadme(readme));
|
233
237
|
};
|
234
238
|
exports.saveTranslatedReadme = saveTranslatedReadme;
|
239
|
+
/**
|
240
|
+
* Extracts paragraphs from a Markdown string.
|
241
|
+
* @param markdownText The input Markdown string.
|
242
|
+
* @returns An array of paragraph contents.
|
243
|
+
*/
|
244
|
+
function extractParagraphs(markdownText) {
|
245
|
+
var _a;
|
246
|
+
const md = new MarkdownIt();
|
247
|
+
const tokens = md.parse(markdownText, {});
|
248
|
+
const paragraphs = [];
|
249
|
+
for (let i = 0; i < tokens.length; i++) {
|
250
|
+
if (tokens[i].type === "paragraph_open" &&
|
251
|
+
((_a = tokens[i + 1]) === null || _a === void 0 ? void 0 : _a.type) === "inline") {
|
252
|
+
paragraphs.push(tokens[i + 1].content);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
return paragraphs;
|
256
|
+
}
|
257
|
+
/**
|
258
|
+
* Splits a paragraph into words and separates each word into syllables.
|
259
|
+
* @param paragraph The input paragraph.
|
260
|
+
* @returns An array of words, each split into syllables.
|
261
|
+
*/
|
262
|
+
function splitIntoSyllables(paragraph) {
|
263
|
+
const words = paragraph.split(/\s+/);
|
264
|
+
const syllables = [];
|
265
|
+
for (const word of words) {
|
266
|
+
syllables.push(...splitWordIntoSyllables(word));
|
267
|
+
}
|
268
|
+
return syllables;
|
269
|
+
}
|
270
|
+
/**
|
271
|
+
* Splits a word into its syllables using a basic estimation.
|
272
|
+
* @param word The word to split.
|
273
|
+
* @returns An array of syllables.
|
274
|
+
*/
|
275
|
+
function splitWordIntoSyllables(word) {
|
276
|
+
const syllableCount = (0, syllable_1.syllable)(word);
|
277
|
+
// Simple heuristic: Split word into equal parts (not perfect, better with a dictionary-based approach)
|
278
|
+
if (syllableCount <= 1)
|
279
|
+
return [word];
|
280
|
+
const approxLength = Math.ceil(word.length / syllableCount);
|
281
|
+
const syllables = [];
|
282
|
+
for (let i = 0; i < word.length; i += approxLength) {
|
283
|
+
// eslint-disable-next-line
|
284
|
+
syllables.push(word.substring(i, i + approxLength));
|
285
|
+
}
|
286
|
+
return syllables;
|
287
|
+
}
|
288
|
+
/**
|
289
|
+
* Extracts words from a given paragraph.
|
290
|
+
* @param paragraph The input text.
|
291
|
+
* @returns An array of words.
|
292
|
+
*/
|
293
|
+
function extractWords(paragraph) {
|
294
|
+
const words = paragraph.match(/\b\w+\b/g); // Match words using regex
|
295
|
+
return words ? words : []; // Return words or an empty array if none found
|
296
|
+
}
|
297
|
+
/**
|
298
|
+
* Calculates the Flesch-Kincaid Grade Level (FKGL) for a given text.
|
299
|
+
* @param text The input paragraph.
|
300
|
+
* @returns The FKGL score.
|
301
|
+
*/
|
302
|
+
function fleschKincaidGrade(text) {
|
303
|
+
const processableText = extractTextFromMarkdown(text);
|
304
|
+
const words = extractWords(processableText);
|
305
|
+
const numWords = words.length;
|
306
|
+
const numSentences = countSentences(processableText);
|
307
|
+
const numSyllables = words.reduce((total, word) => total + (0, syllable_1.syllable)(word), 0);
|
308
|
+
if (numWords === 0 || numSentences === 0) {
|
309
|
+
return {
|
310
|
+
text,
|
311
|
+
fkgl: 0,
|
312
|
+
};
|
313
|
+
}
|
314
|
+
const fkgl =
|
315
|
+
// eslint-disable-next-line
|
316
|
+
0.39 * (numWords / numSentences) + 11.8 * (numSyllables / numWords) - 15.59;
|
317
|
+
return {
|
318
|
+
text,
|
319
|
+
fkgl: parseFloat(fkgl.toFixed(2)),
|
320
|
+
};
|
321
|
+
}
|
322
|
+
/**
|
323
|
+
* Counts the number of sentences in a given text.
|
324
|
+
* @param text The input paragraph.
|
325
|
+
* @returns The total number of sentences.
|
326
|
+
*/
|
327
|
+
function countSentences(text) {
|
328
|
+
const sentences = text
|
329
|
+
.split(/[!.?]+/)
|
330
|
+
.filter(sentence => sentence.trim().length > 0);
|
331
|
+
return sentences.length;
|
332
|
+
}
|
333
|
+
function howManyDifficultParagraphs(paragraphs, maxFKGL) {
|
334
|
+
return paragraphs.filter(paragraph => paragraph.fkgl > maxFKGL).length;
|
335
|
+
}
|
@@ -56,7 +56,9 @@ type TReduceReadmeInputs = {
|
|
56
56
|
lesson: string;
|
57
57
|
number_of_words: string;
|
58
58
|
expected_number_words: string;
|
59
|
+
fkgl_results: string;
|
60
|
+
expected_grade_level: string;
|
59
61
|
};
|
60
|
-
export declare function
|
62
|
+
export declare function makeReadmeReadable(rigoToken: string, inputs: TReduceReadmeInputs): Promise<any>;
|
61
63
|
export declare const isValidRigoToken: (rigobotToken: string) => Promise<boolean>;
|
62
64
|
export {};
|
package/lib/utils/rigoActions.js
CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isValidRigoToken = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
|
4
4
|
exports.downloadImage = downloadImage;
|
5
5
|
exports.createPreviewReadme = createPreviewReadme;
|
6
|
-
exports.
|
6
|
+
exports.makeReadmeReadable = makeReadmeReadable;
|
7
7
|
const axios_1 = require("axios");
|
8
8
|
const fs_1 = require("fs");
|
9
9
|
const console_1 = require("../utils/console");
|
@@ -176,7 +176,7 @@ async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeCo
|
|
176
176
|
});
|
177
177
|
fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer);
|
178
178
|
}
|
179
|
-
async function
|
179
|
+
async function makeReadmeReadable(rigoToken, inputs) {
|
180
180
|
try {
|
181
181
|
const response = await axios_1.default.post(`${RIGOBOT_HOST}/v1/prompting/completion/588/`, { inputs, include_purpose_objective: false, execute_async: false }, {
|
182
182
|
headers: {
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.35","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
|