Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit dab6a11

Browse files
authored
Merge pull request #1396 from ascholerChemeketa/code-coach-modularize
Code coach modularize
2 parents a0fc264 + 180192e commit dab6a11

File tree

3 files changed

+96
-57
lines changed

3 files changed

+96
-57
lines changed

runestone/activecode/js/activecode-i18n.en.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,6 @@ $.i18n().load({
114114
msg_activecode_assertion_error_fix:
115115
"Check the expression to the right of assert. The expression is False and you will need to determine why that is. You may want to simply print out the individual parts of the expression to understand why it is evaluating to False.",
116116
msg_activecode_load_db: "Loading DB...",
117+
msg_activecode_code_coach: "Code Coach",
117118
},
118119
});

runestone/activecode/js/activecode.js

Lines changed: 40 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import "codemirror/addon/hint/anyword-hint.js";
3030
import "codemirror/addon/edit/matchbrackets.js";
3131
import "./skulpt.min.js";
3232
import "./skulpt-stdlib.js";
33+
import PyflakesCoach from "./coach-python-pyflakes.js";
3334
// Used by Skulpt.
3435
import embed from "vega-embed";
3536
// Adapt for use outside webpack -- see https://github.com/vega/vega-embed.
@@ -65,6 +66,7 @@ export class ActiveCode extends RunestoneBase {
6566
this.python3 = true;
6667
this.origElem = orig;
6768
this.origText = this.origElem.textContent;
69+
this.codeCoachList = []; //list of CodeCoaches that will be used to provide feedback
6870
this.divid = opts.orig.id;
6971
this.code = $(orig).text() || "\n\n\n\n\n";
7072
this.language = $(orig).data("lang");
@@ -92,7 +94,7 @@ export class ActiveCode extends RunestoneBase {
9294
}
9395
this.output = null; // create pre for output
9496
this.graphics = null; // create div for turtle graphics
95-
this.codecoach = null;
97+
this.codecoach = null; // div for Code Coaches
9698
this.codelens = null;
9799
this.controlDiv = null;
98100
this.historyScrubber = null;
@@ -137,6 +139,12 @@ export class ActiveCode extends RunestoneBase {
137139
this.caption = "ActiveCode";
138140
}
139141
this.addCaption("runestone");
142+
143+
//Setup CodeCoaches - add based on language
144+
if (this.language == "python" || this.language == "python3") {
145+
this.codeCoachList.push(new PyflakesCoach());
146+
}
147+
140148
setTimeout(
141149
function () {
142150
this.editor.refresh();
@@ -274,6 +282,7 @@ export class ActiveCode extends RunestoneBase {
274282
if (this.logResults) {
275283
this.logCurrentAnswer();
276284
}
285+
this.runCoaches();
277286
this.renderFeedback();
278287
// The run is finished; re-enable the button.
279288
this.runButton.disabled = false;
@@ -722,15 +731,11 @@ export class ActiveCode extends RunestoneBase {
722731
}.bind(this)
723732
);
724733

725-
//Anything that wants to add output to coachdiv can do so after the h3
726-
// all those elements will be cleared with each run and coach display will be
727-
// reset to none. Any component that adds content after a run should set display
728-
// to block to ensure visibility
729734
var coachDiv = document.createElement("div");
730735
coachDiv.classList.add("alert", "alert-warning", "codecoach");
731736
$(coachDiv).css("display", "none");
732737
let coachHead = coachDiv.appendChild(document.createElement("h3"));
733-
coachHead.textContent = "Code Coach";
738+
coachHead.textContent = $.i18n("msg_activecode_code_coach");
734739
this.outerDiv.appendChild(coachDiv);
735740
this.codecoach = coachDiv;
736741

@@ -1222,6 +1227,35 @@ Yet another is that there is an internal error. The internal error message is:
12221227
}
12231228
}
12241229

1230+
async runCoaches() {
1231+
//Run all available code coaches and update code coach div
1232+
1233+
//clear anything after header in codecoach div and hide it
1234+
$(this.codecoach).children().slice(1).remove();
1235+
$(this.codecoach).css("display", "none");
1236+
1237+
//get code, run coaches
1238+
let code = await this.buildProg(false);
1239+
let results = [];
1240+
for(let coach of this.codeCoachList) {
1241+
results.push(coach.check(code));
1242+
}
1243+
1244+
//once all coaches are done, update div
1245+
Promise.allSettled(results).then((promises) => {
1246+
for(let p of promises) {
1247+
if(p.status === 'fulfilled' && p.value !== null && p.value.trim() !== "") {
1248+
let checkDiv = document.createElement("div");
1249+
checkDiv.classList.add("python_check_results");
1250+
let checkPre = checkDiv.appendChild(document.createElement("pre"));
1251+
checkPre.textContent = p.value;
1252+
this.codecoach.append(checkDiv);
1253+
$(this.codecoach).css("display", "block");
1254+
}
1255+
}
1256+
});
1257+
}
1258+
12251259
renderFeedback() {
12261260
// The python unit test code builds the table as it is running the tests
12271261
// In "normal" usage this is displayed immediately.
@@ -1271,51 +1305,6 @@ Yet another is that there is an internal error. The internal error message is:
12711305
}
12721306
}
12731307

1274-
async checkPythonSyntax() {
1275-
let code = this.editor.getValue();
1276-
fetch('/ns/coach/python_check', {
1277-
method: 'POST',
1278-
body: code
1279-
})
1280-
.then((response) => {
1281-
return response.json();
1282-
})
1283-
.then((data) => {
1284-
if(data.trim() !== '') {
1285-
//clean up returned text
1286-
let errorLines = data.split("\n");
1287-
let codeLines = code.split("\n");
1288-
let message = "";
1289-
for(let line of errorLines) {
1290-
if(line.indexOf(".py:") != -1) {
1291-
//old pyflakes returns "file:line:col error"
1292-
//new pyflakes returns "file:line:col: error"
1293-
//handle either
1294-
const cleaner = /[^.]*.py:(\d+):(\d+):? (.*)/i;
1295-
let lineParts = line.match(cleaner)
1296-
message += "Line " + lineParts[1] + ": " + lineParts[3] + "\n";
1297-
message += codeLines[lineParts[1] - 1] + "\n";
1298-
message += " ".repeat(lineParts[2] - 1) + "^\n";
1299-
} else {
1300-
message += line + "\n";
1301-
}
1302-
}
1303-
message = message.slice(0,-1); //remove trailing newline
1304-
1305-
//Render
1306-
let checkDiv = document.createElement("div");
1307-
checkDiv.classList.add("python_check_results");
1308-
let checkPre = checkDiv.appendChild(document.createElement("pre"));
1309-
checkPre.textContent = message;
1310-
this.codecoach.append(checkDiv);
1311-
$(this.codecoach).css("display", "block");
1312-
}
1313-
})
1314-
.catch(err => {
1315-
console.log("Error with ajax python check:", err);
1316-
});
1317-
}
1318-
13191308
/* runProg has several async elements to it.
13201309
* 1. Skulpt runs the python program asynchronously
13211310
* 2. The history is restored asynchronously
@@ -1342,9 +1331,6 @@ Yet another is that there is an internal error. The internal error message is:
13421331
this.saveCode = "True";
13431332
$(this.output).text("");
13441333

1345-
//clear anything after header in codecoach
1346-
$(this.codecoach).children().slice(1).remove();
1347-
13481334
while ($(`#${this.divid}_errinfo`).length > 0) {
13491335
$(`#${this.divid}_errinfo`).remove();
13501336
}
@@ -1386,9 +1372,6 @@ Yet another is that there is an internal error. The internal error message is:
13861372
queue: false,
13871373
});
13881374
}
1389-
if (this.language == "python" || this.language == "python3") {
1390-
this.checkPythonSyntax();
1391-
}
13921375
try {
13931376
await Sk.misceval.asyncToPromise(function () {
13941377
return Sk.importMainWithBody("<stdin>", false, prog, true);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
$.i18n().load({
2+
en: {
3+
msd_pyflakes_coach_line: "Line",
4+
},
5+
});
6+
7+
export default class PyflakesCoach {
8+
async check(code) {
9+
let promise = new Promise(function (resolve, reject) {
10+
fetch('/ns/coach/python_check', {
11+
method: 'POST',
12+
body: code
13+
})
14+
.then((response) => {
15+
return response.json();
16+
})
17+
.then((data) => {
18+
if(data.trim() !== '') {
19+
let message = "";
20+
//clean up returned text
21+
let errorLines = data.split("\n");
22+
let codeLines = code.split("\n");
23+
for(let line of errorLines) {
24+
if(line.indexOf(".py:") != -1) {
25+
//old pyflakes returns "file:line:col error"
26+
//new pyflakes returns "file:line:col: error"
27+
//handle either
28+
const cleaner = /[^.]*.py:(\d+):(\d+):? (.*)/i;
29+
let lineParts = line.match(cleaner); //[1]: line, [2]: col, [3]: error
30+
31+
//for now, filter messages about star imports
32+
if(!lineParts[3].includes("defined from star imports")
33+
&& !lineParts[3].includes("*' used; unable to detect undefined names"))
34+
{
35+
message += $.i18n("msd_pyflakes_coach_line") + lineParts[1] + ": " + lineParts[3] + "\n";
36+
message += codeLines[lineParts[1] - 1] + "\n";
37+
message += " ".repeat(lineParts[2] - 1) + "^\n";
38+
}
39+
} else {
40+
message += line + "\n";
41+
}
42+
}
43+
message = message.slice(0,-1); //remove trailing newline
44+
resolve(message);
45+
}
46+
resolve(null);
47+
})
48+
.catch(err => {
49+
reject("Error in Pyflakes Coach: " + err);
50+
})
51+
});
52+
return promise;
53+
}
54+
}
55+

0 commit comments

Comments
 (0)