diff --git a/.vscode/launch.json b/.vscode/launch.json index 0aa65c7..cd56bf2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,6 +29,16 @@ "skipFiles": ["/**"], "type": "node", "console": "integratedTerminal" + }, + { + "name": "Python: current file", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "env": { + "PYTHONPATH": "${workspaceFolder}" + } } ] } diff --git a/build.py b/build.py index dbbb06e..a42bbfa 100644 --- a/build.py +++ b/build.py @@ -1,17 +1,75 @@ +#!/usr/bin/env python3 + """ +pySELL - Python based Simple E-Learning Language for the simple creation of + interactive courses +AUTHOR: Andreas Schwenk +LICENSE: GPLv3 + This script is only intended for pySELL development. -Users just use file 'sell.py' +Users just use file 'sell.py' (python sell.py QUESTION_FILE.txt) """ import subprocess -try: - res = subprocess.run(["npm", "install"], cwd="web") - res = subprocess.run(["node", "build.js"], cwd="web") -except Exception as e: - print(e) - print("pySELL dependencies: npm+nodejs") - print(" https://www.npmjs.com, https://nodejs.org/") - print(" https://nodejs.org/en/download/package-manager") - print("[Debian] sudo apt install nodejs npm") - print("[macOS] brew install node") +print("pySELL builder - 2024 by Andreas Schwenk") + +if __name__ == "__main__": + # build web + try: + # install web dependencies + res = subprocess.run(["npm", "install"], cwd="web") + # build web + res = subprocess.run(["node", "build.js"], cwd="web") + except Exception as e: + print(e) + print("pySELL dependencies: npm+nodejs") + print(" https://www.npmjs.com, https://nodejs.org/") + print(" https://nodejs.org/en/download/package-manager") + print("[Debian] sudo apt install nodejs npm") + print("[macOS] brew install node") + # build html template and update sell.py + f = open("web/index.html") + index_html_lines = f.readlines() + f.close() + f = open("web/dist/sell.min.js") + js = f.read().strip() + f.close() + f = open("sell.py") + sell_py_lines = f.readlines() + f.close() + # remove code between @begin(test) and @end(test) + html = "" + skip = False + for line in index_html_lines: + if "@begin(test)" in line: + skip = True + elif "@end(test)" in line: + skip = False + elif skip is False: + html += line + # remove white spaces + html = html.replace(" ", "").replace("\n", " ") + # insert javascript code + html = html.replace( + "", + "", + ) + # update file "sell.py" between "# @begin(html" and "# @end(html)" + py = "" + skip = False + for line in sell_py_lines: + if "@begin(html)" in line: + skip = True + elif "@end(html)" in line: + skip = False + py += "# @begin(html)\n" + py += 'html = """' + html.strip() + '\n"""\n' + py += "# @end(html)\n" + elif skip is False: + py += line + # write new version of sell.py + f = open("sell__TESTXXX.py", "w") + f.write(py.strip() + "\n") diff --git a/sell.py b/sell.py index a8dcaca..1bbf7dc 100644 --- a/sell.py +++ b/sell.py @@ -1,10 +1,24 @@ #!/usr/bin/env python3 """ -pySELL - Python based Simple E-Learning Language - Push interactive STEM tasks to the web -AUTHOR: Andreas Schwenk -LICENSE: GPLv3 +======= pySELL ================================================================= + + A Python based Simple E-Learning Language + for the simple creation of interactive courses + +AUTHOR Andreas Schwenk + +LICENSE GPLv3 + +Docs: Refer to https://github.com/andreas-schwenk/pysell and read the + descriptions at the end of the page + +Usage: Only file 'sell.py' is required to compile question files + + COMMAND python3 [-J] sell.py PATH + ARGUMENTS -J is optional and generates a JSON output file for debugging + EXAMPLE python3 sell.py examples/ex1.txt + OUTPUT examples/ex1.html, examples/ex1_DEBUG.html """ import json, sys, os, re @@ -651,7 +665,7 @@ def compile(src: str) -> dict: html = """ pySELL

DEBUG VERSION

This quiz was created using pySELL, the Python-based Simple E-Learning Language, written by Andreas Schwenk, GPLv3
last update on

+ `.includes(i)==!1&&(this.token+=i),["x","y","z","t"].includes(this.token)&&(e=!0),this.pos++}}isNum(e){return e.charCodeAt(0)>=48&&e.charCodeAt(0)<=57}isAlpha(e){return e.charCodeAt(0)>=65&&e.charCodeAt(0)<=90||e.charCodeAt(0)>=97&&e.charCodeAt(0)<=122||e==="_"}};var H=class{constructor(e,t=!1){this.src=e,this.debug=t,this.instanceIdx=Math.floor(Math.random()*e.instances.length),this.choiceIdx=0,this.gapIdx=0,this.expected={},this.types={},this.student={},this.inputs={},this.qDiv=null,this.titleDiv=null,this.checkBtn=null,this.showSolution=!1}populateDom(e){if(this.qDiv=M(),e.appendChild(this.qDiv),this.qDiv.classList.add("question"),this.titleDiv=M(),this.qDiv.appendChild(this.titleDiv),this.titleDiv.classList.add("questionTitle"),this.titleDiv.innerHTML=this.src.title,this.src.error.length>0){let r=v(this.src.error);this.qDiv.appendChild(r),r.style.color="red";return}for(let r of this.src.text.children)this.qDiv.appendChild(this.generateText(r));let t=M();this.qDiv.appendChild(t),t.classList.add("buttonRow");let i=Object.keys(this.expected).length>0;i&&(this.checkBtn=j(),t.appendChild(this.checkBtn),this.checkBtn.innerHTML=Y);let s=v("   ");t.appendChild(s);let n=v("");if(t.appendChild(n),this.debug){if(this.src.variables.length>0){let a=M();a.classList.add("debugInfo"),a.innerHTML="Variables generated by Python Code",this.qDiv.appendChild(a);let p=M();p.classList.add("debugCode"),this.qDiv.appendChild(p);let u=this.src.instances[this.instanceIdx],f="",h=[...this.src.variables];h.sort();for(let c of h){let d=u[c].type,m=u[c].value;switch(d){case"vector":m="["+m+"]";break;case"set":m="{"+m+"}";break}f+=d+" "+c+" = "+m+"
"}p.innerHTML=f}let r=["python_src_html","text_src_html"],o=["Python Source Code","Text Source Code"];for(let a=0;a0){let u=M();u.classList.add("debugInfo"),u.innerHTML=o[a],this.qDiv.appendChild(u);let f=M();f.classList.add("debugCode"),this.qDiv.append(f),f.innerHTML=this.src[p]}}}i&&this.checkBtn.addEventListener("click",()=>{n.innerHTML="";let r=0,o=0;for(let a in this.expected){let p=this.types[a],u=this.student[a],f=this.expected[a];switch(p){case"bool":u===f&&o++;break;case"string":{let h=this.inputs[a],c=u.trim().toUpperCase(),d=f.trim().toUpperCase(),m=c===d;m&&o++,h.style.color=m?"black":"white",h.style.backgroundColor=m?"transparent":"red";break}case"int":case"float":Math.abs(parseFloat(u)-parseFloat(f))<1e-9&&o++;break;case"term":{try{let h=new L;h.parse(f);let c=new L;c.parse(u),h.compare(c)&&o++}catch(h){this.debug&&console.log(h)}break}case"vector":case"complex":case"set":{f=f.split(","),r+=f.length-1,u=[];for(let h=0;h=5&&(n.innerHTML=""+o+" / "+r))})}generateMathString(e){let t="";switch(e.type){case"math":case"display-math":for(let i of e.children)t+=this.generateMathString(i);break;case"text":return e.data;case"var":{let i=this.src.instances[this.instanceIdx],s=i[e.data].type,n=i[e.data].value;switch(s){case"vector":return"\\left["+n+"\\right]";case"set":return"\\left\\{"+n+"\\right\\}";case"complex":{let r=n.split(","),o=parseFloat(r[0]),a=parseFloat(r[1]),p="";return Math.abs(o)>1e-9&&(p+=o),Math.abs(a)>1e-9&&(p+=(a<0?"-":"+")+a+"i"),p}case"matrix":{let r=new T(0,0);return r.fromString(n),t=r.toTeX(e.data.includes("augmented")),t}case"term":{t=n.replaceAll("sin","\\sin").replaceAll("cos","\\cos").replaceAll("tan","\\tan").replaceAll("exp","\\exp").replaceAll("ln","\\ln").replaceAll("*","\\cdot ").replaceAll("(","\\left(").replaceAll(")","\\right)");break}default:t=n}}}return t}generateMatrixParenthesis(e,t){let i=document.createElement("td");i.style.width="3px";for(let s of["Top",e?"Left":"Right","Bottom"])i.style["border"+s+"Width"]="2px",i.style["border"+s+"Style"]="solid";return i.rowSpan=t,i}validateTermInput(e){let t=new L,i=!0,s=e.value;if(s.length>0)try{t.parse(s)}catch{i=!1}e.style.color=i?"black":"maroon"}generateText(e,t=!1){switch(e.type){case"paragraph":case"span":{let i=document.createElement(e.type=="span"||t?"span":"p");for(let s of e.children)i.appendChild(this.generateText(s));return i}case"text":return v(e.data);case"code":{let i=v(e.data);return i.classList.add("code"),i}case"italic":case"bold":{let i=v("");return i.append(...e.children.map(s=>this.generateText(s))),e.type==="bold"?i.style.fontWeight="bold":i.style.fontStyle="italic",i}case"math":case"display-math":{let i=this.generateMathString(e);return E(i,e.type==="display-math")}case"gap":{let i=v(""),s=Math.max(e.data.length*12,24),n=D(s),r="gap-"+this.gapIdx;return this.inputs[r]=n,this.expected[r]=e.data,this.types[r]="string",n.addEventListener("keyup",()=>{this.student[r]=n.value.trim()}),this.showSolution&&(this.student[r]=n.value=this.expected[r]),this.gapIdx++,i.appendChild(n),i}case"input":case"input2":{let i=e.type==="input2",s=v("");s.style.verticalAlign="text-bottom";let n=e.data,r=this.src.instances[this.instanceIdx][n];if(this.expected[n]=r.value,this.types[n]=r.type,!i)switch(r.type){case"set":s.append(E("\\{"),v(" "));break;case"vector":s.append(E("["),v(" "));break}if(r.type==="vector"||r.type==="set"){let o=r.value.split(","),a=o.length;for(let p=0;p0&&s.appendChild(v(" , "));let u=D(Math.max(o[p].length*12,24));s.appendChild(u),u.addEventListener("keyup",()=>{this.student[n+"-"+p]=u.value.trim(),this.validateTermInput(u)}),this.showSolution&&(this.student[n+"-"+p]=u.value=o[p])}}else if(r.type==="matrix"){let o=(f,h,c)=>{let d=M();f.innerHTML="",f.appendChild(d),d.style.position="relative",d.style.display="inline-block";let m=document.createElement("table");d.appendChild(m);let I=h.getMaxCellStrlen();I=Math.max(I*12,24);for(let g=0;g{this.student[n+"-"+z]=A.value.trim(),this.validateTermInput(A)}),this.showSolution&&(this.student[n+"-"+z]=A.value=""+c.v[z])}g==0&&k.appendChild(this.generateMatrixParenthesis(!1,c.m))}let w=["+","-","+","-"],y=[0,0,1,-1],B=[1,-1,0,0],C=[0,22,888,888],S=[888,888,-22,-22],U=[-22,-22,0,22],G=[h.n!=1,h.n!=1,h.m!=1,h.m!=1],J=[c.n>=10,c.n<=1,c.m>=10,c.m<=1];for(let g=0;g<4;g++){if(G[g]==!1)continue;let k=v(w[g]);C[g]!=888&&(k.style.top=""+C[g]+"px"),S[g]!=888&&(k.style.bottom=""+S[g]+"px"),U[g]!=888&&(k.style.right=""+U[g]+"px"),k.classList.add("matrixResizeButton"),d.appendChild(k),J[g]?k.style.opacity="0.5":k.addEventListener("click",()=>{c.resize(c.m+y[g],c.n+B[g],"0"),o(f,h,c)})}},a=new T(0,0);a.fromString(r.value);let p=new T(a.m==1?1:3,a.n==1?1:3);this.showSolution&&p.fromMatrix(a);let u=M();s.appendChild(u),o(u,a,p)}else if(r.type==="complex"){let o=r.value.split(",");for(let a=0;a<2;a++){let p=D(Math.max(Math.max(o[a].length*12,24),24));s.appendChild(p),this.showSolution&&(this.student[n+"-"+a]=p.value=o[a]),p.addEventListener("keyup",()=>{this.student[n+"-"+a]=p.value.trim(),this.validateTermInput(p)}),a==0?s.append(v(" "),E("+"),v(" ")):s.append(v(" "),E("i"))}}else{let o=D(Math.max(r.value.length*12,24));s.appendChild(o),o.addEventListener("keyup",()=>{this.student[n]=o.value.trim(),this.validateTermInput(o)}),this.showSolution&&(this.student[n]=o.value=r.value)}if(!i)switch(r.type){case"set":s.append(v(" "),E("\\}"));break;case"vector":s.append(v(" "),E("]"));break}return s}case"itemize":return R(e.children.map(i=>W(this.generateText(i))));case"single-choice":case"multi-choice":{let i=e.type=="multi-choice",s=document.createElement("table"),n=e.children.length,r=this.debug?K(n):$(n),o=i?Z:X,a=i?O:Q,p=[],u=[];for(let f=0;f{this.student[d]=this.student[d]==="true"?"false":"true",this.student[d]==="true"?y.innerHTML=o:y.innerHTML=a}):w.addEventListener("click",()=>{for(let C of u)this.student[C]="false";this.student[d]="true";for(let C=0;C";document.getElementById("courseInfo2").innerHTML=N[l.lang].replace("*",t);let i=[],s=document.getElementById("questions"),n=1;for(let r of l.questions){r.title=""+n+". "+r.title;let o=new H(r,e);o.showSolution=e,i.push(o),o.populateDom(s),e&&r.error.length==0&&o.checkBtn.click(),n++}}return re(ae);})();sell.init(quizSrc,debug); """ # @end(html) html = html.replace("\\", "\\\\") diff --git a/todo.txt b/todo.txt index ad8fcad..31da11e 100644 --- a/todo.txt +++ b/todo.txt @@ -8,8 +8,6 @@ # TODO: publish python package -# TODO: include description from README.md into sell.py preamble - # TODO: test "\n" in Windows (must replace "\r" after reading file??) # TODO: large feedback text "awesome, try again, ..." diff --git a/web/build.js b/web/build.js index ed4d095..658a1b3 100644 --- a/web/build.js +++ b/web/build.js @@ -1,7 +1,6 @@ -import fs from "fs"; import esbuild from "esbuild"; -// build javascript +// build minified javascript file dist/sell.min.js esbuild.buildSync({ platform: "browser", globalName: "sell", @@ -11,41 +10,3 @@ esbuild.buildSync({ bundle: true, outfile: "dist/sell.min.js", }); - -process.exit(0); - -// build html template and update sell.py -let lines = fs.readFileSync("index.html", "utf-8").split("\n"); -let js = fs.readFileSync("dist/sell.min.js", "utf-8").trim(); -// remove code between @begin(test) and @end(test) -let html = ""; -let skip = false; -for (let line of lines) { - if (line.includes("@begin(test)")) skip = true; - else if (line.includes("@end(test)")) skip = false; - else if (skip == false) html += line + "\n"; -} -// remove white spaces -html = html.replaceAll(" ", "").replaceAll("\n", " "); -// insert javascript code -html = html.replace( - "", - "" -); -// update file "sell.py" between "# @begin(html" and "# @end(html)" -lines = fs.readFileSync("sell.py", "utf-8").split("\n"); -let py = ""; -skip = false; -for (let line of lines) { - if (line.includes("@begin(html)")) skip = true; - else if (line.includes("@end(html)")) { - skip = false; - py += "# @begin(html)\n"; - py += 'html = """' + html + '\n"""\n'; - py += "# @end(html)\n"; - } else if (skip == false) py += line + "\n"; -} -// write new version of sell.py -fs.writeFileSync("sell.py", py.trim() + "\n"); diff --git a/web/src/dom.js b/web/src/dom.js index fedca5d..176dae6 100644 --- a/web/src/dom.js +++ b/web/src/dom.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/ diff --git a/web/src/icons.js b/web/src/icons.js index 78c6bd4..bcd8ebc 100644 --- a/web/src/icons.js +++ b/web/src/icons.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/ diff --git a/web/src/index.js b/web/src/index.js index cac9044..4e19081 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/ diff --git a/web/src/lang.js b/web/src/lang.js index 82f65c3..de4639c 100644 --- a/web/src/lang.js +++ b/web/src/lang.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/ diff --git a/web/src/math.js b/web/src/math.js index c51a87d..2b8a2f7 100644 --- a/web/src/math.js +++ b/web/src/math.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/ diff --git a/web/src/math_TEST.js b/web/src/math_TEST.js index d159410..4968764 100644 --- a/web/src/math_TEST.js +++ b/web/src/math_TEST.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/ diff --git a/web/src/question.js b/web/src/question.js index 8534ffb..c5e26d4 100644 --- a/web/src/question.js +++ b/web/src/question.js @@ -1,5 +1,5 @@ /******************************************************************************* - * SELL - Simple E-Learning Language + * pySELL - Python based Simple E-Learning Language * AUTHOR: Andreas Schwenk * LICENSE: GPLv3 ******************************************************************************/