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

Commit bd74a56

Browse files
Refactor Pyflakes code coach
1 parent 5d30f41 commit bd74a56

File tree

3 files changed

+91
-12
lines changed

3 files changed

+91
-12
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 & 12 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) {
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.
@@ -1342,9 +1376,6 @@ Yet another is that there is an internal error. The internal error message is:
13421376
this.saveCode = "True";
13431377
$(this.output).text("");
13441378

1345-
//clear anything after header in codecoach
1346-
$(this.codecoach).children().slice(1).remove();
1347-
13481379
while ($(`#${this.divid}_errinfo`).length > 0) {
13491380
$(`#${this.divid}_errinfo`).remove();
13501381
}
@@ -1386,9 +1417,6 @@ Yet another is that there is an internal error. The internal error message is:
13861417
queue: false,
13871418
});
13881419
}
1389-
if (this.language == "python" || this.language == "python3") {
1390-
this.checkPythonSyntax();
1391-
}
13921420
try {
13931421
await Sk.misceval.asyncToPromise(function () {
13941422
return Sk.importMainWithBody("<stdin>", false, prog, true);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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)
30+
31+
message += $.i18n("msd_pyflakes_coach_line") + lineParts[1] + ": " + lineParts[3] + "\n";
32+
message += codeLines[lineParts[1] - 1] + "\n";
33+
message += " ".repeat(lineParts[2] - 1) + "^\n";
34+
} else {
35+
message += line + "\n";
36+
}
37+
}
38+
message = message.slice(0,-1); //remove trailing newline
39+
resolve(message);
40+
}
41+
resolve(null);
42+
})
43+
.catch(err => {
44+
reject("Error in Pyflakes Coach: " + err);
45+
})
46+
});
47+
return promise;
48+
}
49+
}
50+

0 commit comments

Comments
 (0)