{"id":34072687,"url":"https://github.com/andreas-schwenk/pysell","last_synced_at":"2026-03-12T19:14:24.796Z","repository":{"id":221746145,"uuid":"755265340","full_name":"andreas-schwenk/pysell","owner":"andreas-schwenk","description":"pySELL - A Python-based Simple E-Learning Language for the Rapid Creation of Interactive and Mobile-Friendly STEM Quizzes","archived":false,"fork":false,"pushed_at":"2025-09-11T16:23:30.000Z","size":3746,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-16T09:05:49.665Z","etag":null,"topics":["classroom-tools","domain-specific-language","dsl","e-learning","learning-management","math","mobile-first","online-courses","quizzes","stem","teaching"],"latest_commit_sha":null,"homepage":"https://pysell.org","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreas-schwenk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-02-09T19:13:30.000Z","updated_at":"2025-09-11T16:23:33.000Z","dependencies_parsed_at":"2025-09-11T18:24:44.686Z","dependency_job_id":"9619df94-78a4-493f-93b5-42d043953210","html_url":"https://github.com/andreas-schwenk/pysell","commit_stats":null,"previous_names":["andreas-schwenk/sell4ever","andreas-schwenk/pysell"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/andreas-schwenk/pysell","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas-schwenk%2Fpysell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas-schwenk%2Fpysell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas-schwenk%2Fpysell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas-schwenk%2Fpysell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreas-schwenk","download_url":"https://codeload.github.com/andreas-schwenk/pysell/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas-schwenk%2Fpysell/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30439658,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T14:34:45.044Z","status":"ssl_error","status_checked_at":"2026-03-12T14:09:33.793Z","response_time":114,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["classroom-tools","domain-specific-language","dsl","e-learning","learning-management","math","mobile-first","online-courses","quizzes","stem","teaching"],"created_at":"2025-12-14T08:15:32.591Z","updated_at":"2026-03-12T19:14:24.790Z","avatar_url":"https://github.com/andreas-schwenk.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pySELL\n\n\u003cimg src=\"https://raw.githubusercontent.com/andreas-schwenk/pysell/refs/heads/main/img/logo.jpg\" width=\"128\" height=\"128\"/\u003e\n\n**WELCOME! VISIT THE WEBSITE [https://pysell.org](https://pysell.org) FOR EXAMPLES AND GUIDES.**\n\n`pySELL` is a Python-based Simple E-Learning Language designed for the rapid creation of interactive STEM quizzes, with a focus on randomized math questions.\n\nQuizzes created with `pySELL` can be used on mobile devices.\n\nCompared to other solutions (e.g., `STACK` questions), `pySELL` has NO technological runtime dependencies, except for `katex` for math rendering. Each generated quiz consists of a single self-contained HTML file. These files can be hosted on a web server or imported into existing LMS courses (e.g., _Moodle_ via \"file upload\" or _Ilias_ via \"HTML course\").\n\nStudent answers are not stored on servers, ensuring that `pySELL` quizzes provide 100% anonymous training. This anonymity is highly appreciated by students when first engaging with new topics.\n\nTeachers benefit from a simple-to-learn syntax. With some practice, even sophisticated questions can be generated with minimal time investment.\n\nIf you are using `pySELL` in one of your (university) classes, I would love to hear about it! Please send feedback, bug reports, or feature requests to [contact@compiler-construction.com](mailto:contact@compiler-construction.com).\n\nAs a member of the Free Software Foundation (FSF), I have decided to publish `pySELL` as free and open-source software under the `GPLv3` license.\n\n![](https://raw.githubusercontent.com/andreas-schwenk/pysell/refs/heads/main/img/example.jpg)\n\n## User Guide\n\nTo install the `pySELL` package from [https://pypi.org/project/pysell/](https://pypi.org/project/pysell/), simply run the following command:\n\n```bash\npip install pysell\n```\n\nIf you've already installed `pySELL`, you can update it to the latest version with the following command:\n\n```bash\npip install --upgrade pysell\n```\n\nRun the following command to generate a self-contained quiz website `FILENAME.html` from the sources in `FILENAME.txt`. An example is provided below, with more examples available in the `examples/` directory.\n\n```bash\npysell FILENAME.txt\n```\n\nAdditionally, a file `FILENAME_debug.html` is created for debugging purposes. The debug output differs from the release files in the following aspects:\n\n- The sample solution is rendered in the input fields\n- All questions are evaluated directly for testing purposes\n- Single and multiple-choice answers are displayed in a static order\n- Python and text sources are displayed with syntax highlighting\n- Line numbers from the source file are shown for each exercise\n\nIf you would like to use `SageMath` in your code, run the following commands for installation and usage:\n\n```bash\nsage -pip install pysell\nsage -python -m pysell FILENAME.txt\n```\n\n### Using the Portable Version of pySELL Without Installation\n\nAlternatively, if you'd prefer not to use a package manager, you can directly download the stand-alone file [`sell.py`](https://raw.githubusercontent.com/andreas-schwenk/pysell/main/sell.py) from the repository. This is the only file required; all other files are used for the development of `pySELL`.\n\nUsage example:\n\n```bash\npython3 sell.py FILENAME.txt\n```\n\n## Dependencies\n\n**Users:** Only vanilla Python 3 is required to create basic questions. If you want to use symbolic calculations in your questions, you should also install `sympy` (`pip install sympy`). For linear algebra, you can use `numpy` (`pip install numpy`). To enable plotting, `matplotlib` is supported (`pip install matplotlib`). You can also use `SageMath` for advanced mathematical computations.\n\n**Developers:** Node.js and a local web server are recommended for debugging the web code. Alternatively, you can install the recommended VS Code extension available in this repository.\n\n## Example\n\nThe following example code generates some questions, as can be seen in the figure. You may run the examples [here](https://andreas-schwenk.github.io/pysell/ex1.html).\n\n**Command:**\n\n```bash\npysell examples/ex1.txt\n```\n\nFiles `ex1.html` and `ex1_DEBUG.html` will be generated. The latter file shows the sample solution.\n\nSome contents of the example file `examples/ex1.txt` are shown below. Get the complete example file [here](https://github.com/andreas-schwenk/pysell/blob/main/examples/ex1.txt):\n\n```\nLANG    en\nTITLE   pySELL Demo\nAUTHOR  Andreas Schwenk\n\n\nQUESTION Multiple-Choice\nMark the correct answer(s)\n[x] This answer is correct\n[x] This answer is correct\n[ ] This answer is incorrect\n\n\nQUESTION Addition\n\"\"\"\nimport random\nx = random.randint(10, 20)\ny = random.randint(10, 20)\nz = x + y\n\"\"\"\nCalculate $x + y =$ %z\n\n\nQUESTION Gaps\n- Write 3 as a word: %\"three\"\n- Write 7 as a word: %\"seven\"\n- Write the name of one of the first two letters in the Greek alphabet: %\"alpha|beta\"\n\n\nQUESTION Lists/Vectors\n\"\"\"\nfib = [1] * 7\nfor i in range(2,len(fib)):\n    fib[i] = fib[i-2] + fib[i-1]\nfib3 = fib[3:]\n\"\"\"\nContinue the Fibonacci sequence\n- $ 1, 1, 2, $ %!fib3, ...\n\n\nQUESTION Terms 2: Integration\n\"\"\"\nfrom sympy import *\nx = symbols('x')\nf = parse_expr(\"(x+1) / exp(x)\", evaluate=False)\ni = integrate(f,x)\n\"\"\"\nDetermine by **partial integration:** \\\\\n- $ \\displaystyle \\int f ~ dx =$ %i $+ C$ \\\\\nwith $C \\in \\RR$\n\n\nQUESTION Matrices with Sympy\n\"\"\"\nfrom sympy import *\nA = randMatrix(3,3, min=-1, max=1, symmetric=True)\nB = randMatrix(2,3, min=-2, max=2, symmetric=False)\nx,y = symbols('x,y')\nB[0,0] = cos(x) + sin(y)\nC = A * B.transpose()\n\"\"\"\n- $A \\cdot B^T=$ %C\n\n\nQUESTION Images\n!../docs/logo.svg:25\nWhat is shown in the image?\n(x) the pySELL logo\n( ) the PostScript logo\n```\n\n### Quiz with time limit\n\nCreate timed quiz pages easily by adding the `TIMER` keyword to the preamble. Once the timer expires, all questions will be automatically evaluated at once.\n\n```\nLANG    en\nTITLE   pySELL demo with time limit\nAUTHOR  Andreas Schwenk\n\nTIMER   30            # all questions will be evaluated when the timer runs out.\n\nQUESTION Addition                 # student earns 1 points per default\n\"\"\"\nimport random\nx = random.randint(-10, 10)\ny = random.randint(1, 10)\nz = x + y\n\"\"\"\nCalculate $x + y =$ %z\n\nQUESTION Multiplication (2 pts)    # student earns 2 points\n\"\"\"\nimport random\nx = random.randint(-10, 10)\ny = random.randint(1, 10)\nz = x * y\n\"\"\"\nCalculate $x \\cdot y =$ %z\n```\n\n![](https://raw.githubusercontent.com/andreas-schwenk/pysell/refs/heads/main/img/example2.jpg)\n\n## Syntax\n\nThis section describes the syntax of `pySELL`. Many aspects are self-explanatory and can be understood from the [example file](https://github.com/andreas-schwenk/pysell/blob/main/examples/ex1.txt).\n\n### Global\n\n- `LANG` defines the natural language used in the few built-in output strings. Currently supported languages are `en`, `de`, `es`, `it`, and `fr`.\n\n- `TITLE` defines the title of the page. You may include HTML code, but everything must be written on the same line where the title keyword starts.\n\n- `AUTHOR` defines the author or institution of the quizzes. You may include HTML code, but everything must be written on the same line where the author keyword starts.\n\n- `QUESTION` indicates the start of a new question, with its title specified on the same line. By default, each correctly answered question earns the student one point. To specify a different point value, include the desired points in parentheses, such as `(X pts)`, where `X` is the number of points. For example: `QUESTION Turing Machine (3 pts)`\n\n- `TIMER` restricts the time students have to complete the quiz page. The time, specified in seconds, is written after a space.\n\n- `##` introduces a comment, i.e., text that is not considered by the compiler (Inside of Python code, a single hashtag is\n  used for comments).\n\n### Questions\n\nA question consists of a textual part and optionally includes Python code that generates random variables and calculates the sample solution.\n\n**Question text**\n\nAll text shown to the student is written as plain text. Formatting options are as follows:\n\n- _Italic text_ is enclosed in single asterisks `*` (e.g., `math is *cool*`).\n\n- **Bold text** is enclosed in double asterisks `**` (e.g., `math can be **challenging**`).\n\n- Embedded code within a paragraph text is enclosed in backticks `` ` ``.\n\n- Embedded code that spans multiple lines is embedded within a pair of triple backticks. The triple backticks must be on separate lines without any other characters on these lines.\n\n- Items in a list are preceded by `-`.\n\n- TeX-based inline math is enclosed in dollar signs `$` (e.g., `$\\sqrt{x^2+y^2}$` for $\\sqrt{x^2+y^2}$).\n\n- TeX-based display style math is enclosed in double dollar signs `$$`. Display mode in inline math can also be activated by writing, e.g., `$\\displaystyle \\sum_{i=1}^n i^2$`.\n\n- Multiple-choice questions use `[x]` for correct answers and `[ ]` for incorrect answers, with text separated by a space (e.g., `[x] This answer is correct`).\n\n- Single-choice questions use `(x)` for the correct answer and `( )` for incorrect answers. Only one answer can be true (e.g., `( ) This answer is incorrect`).\n\n- A line break can be forced with `\\\\` at the end of a line (e.g., `A new paragraph will start after this line. \\\\`).\n\n- Static images can be included with `!`, followed by the path and optionally the width in percentage (path and width are separated by `:`). For example, `!myImage.svg:25` shows the image located at `myImage.svg` with a width of `25%` relative to the question box. If the width is omitted, `100%` is assumed. Supported image formats are `svg`, `png`, and `jpg`. Note that image data is directly embedded into the output files, so you do not need to publish them separately. Be mindful of image file sizes. SVG files are usually very small for vector graphics (hint: use the tool `pdf2svg` to generate SVG files from PDF files. The latter can be generated by `tikZ`). For dynamic plots via `matplotlib`, refer to the next section.\n\n**Question code**\n\nTo generate randomized variables, arbitrary Python code can be evaluated (this is secure, as the code is executed only locally on the teacher's computer).\n\nFor each question that includes randomization (the compiler checks if your Python code contains the string `rand`), 5 distinct instances are drawn. If your randomization is poor, some instances may be identical. If no random numbers are used, only one instance will be created.\n\n- Python code is embedded within a pair of triple quotes `\"\"\"`. The triple quotes must be on separate lines without any other characters on these lines. Python code must be provided **before** its variables are accessed in the textual part.\n\n- Variables denoted in math mode are replaced by their actual values (the execution environment randomly selects one of the 5 instances). This behavior can be suppressed by embedding the variable name in double quotes (e.g., write `\"x\"` instead of just `x`).\n\n**Warning: Variable names with underscores (e.g., `x_1`) are not allowed, as the underscore can cause ambiguity in TeX.**\n\n- Input fields are generated using `%`, followed by the variable name. The structure of the input field depends on the type of the variable (`int`, `set`, `numpy.array`, etc.). If the variable is non-scalar, parentheses (or brackets, or braces) will be rendered around vectors/sets/etc. To suppress these parentheses, write `%!` followed by the variable name. This is used, for example, in the _Fibonacci_ example in the example file. Example: `The answer is %answer`.\n\n- In general, variables can only be accessed within math mode (i.e., in `$...$`). To use Python-generated variables of type `str` (strings) seamlessly in the question text, use the ampersand operator `\u0026`, followed by the variable name in text mode. Example: `Today I feel \u0026mood`.\n\n- To create gap questions, use `%` followed by the expected word(s), enclosed in double quotes (e.g., `%\"three\"`). To accept multiple answers, separate the words with the pipe operator `|`. Example: `%\"three|tres|trois|tre\"`.\n\n- Dynamic gaps can be created with Python code. Generate a string variable (e.g., `answer = \"three|tres\"`) and ask it exactly as for number inputs (e.g., `%answer`).\n\n- For static or dynamic plots, refer to the `Plot` example in the examples. `pySELL` supports using `matplotlib`.\n\n_Hint: If a question has no input fields, the evaluation button is not shown._\n\n**Important notes**\n\n- Consider excluding certain Python variables from the output. For example, `matplotlib` requires defining axes, and the $x$-vector `x = np.linspace(-10,10,1000)` has a length of 1000. By default, `pySELL` will include this in the question database. To prevent this, you should write `del x` at the end of your Python code to exclude `x`.\n\n- In general, you may import arbitrary Python libraries. `pySELL` will attempt to map data types to its internal data types (e.g., some commonly used `sage` data types are mapped). For all unimplemented types, the variable is considered a _term_ and the value is exported using `str(my_var)`. This may work or may not. Feel free to ask the author of `pySELL` to extend support for missing or exotic data types.\n\n\u003c!-- TODO: write about types (impl is WIP):\nint, float, set, matrix\n--\u003e\n\n### LLM generated questions\n\nGenerating questions can be time-consuming, but Large Language Models (LLMs) like ChatGPT can assist.\n\nUse the following prompt to generate questions:\n\n```\nGenerate 10 questions for students in a math course on the topic of complex numbers using the pySELL formal language. The pySELL language is defined here: https://raw.githubusercontent.com/andreas-schwenk/pysell/main/llm.md. Ensure that each question is correctly formatted according to the pySELL specification and covers a range of topics related to complex numbers, including arithmetic operations, modulus, argument, conjugate, and forms of representation.\n```\n\n_Note that the specification in the `llm.md` file is not yet complete. Additionally, the quality of generated questions may not be perfect and may require human post-correction._\n\n### Hints on generating random variables\n\n_Note: also read about the custom function `rangeZ`, to exclude the zero from a range, below._\n\nRead the docs:\n\n- [https://docs.python.org/3/library/random.html](https://docs.python.org/3/library/random.html)\n\nExamples:\n\n#### Draw a random integer `a` from `{-2,-1,...,5}`:\n\n```python\nimport random\na = random.randint(-2,5)\n# equivalent:\na = random.choice(range(-2,5+1))\n```\n\n_The examples explicitly write `+1` to clarify that the upper bound is not included._\n\n#### Choose a random number `a` from set `{2,3,5,7}`:\n\n```python\nimport random\na = random.choice([2,3,5,7])\n```\n\n_Note that the parameter is actually a list._\n\n#### Draw 3 random integers `a`, `b`, `c` from `{-2,-1,...,5}` with replacement:\n\n```python\nimport random\n# store in a, b, c\n[a,b,c] = random.choices(range(-2,5+1),k=3)\n# store as array x\nx = random.choices(range(-2,5+1),k=3)\n```\n\n_Note that the upper bound of `range` is **excluded**._\n\n#### Draw 3 **unique** (i.e. without replacement) random integers `a`, `b`, `c` from `{-2,-1,...,5}`:\n\n```python\nimport random\n# store in scalar variables a, b, c\n[a,b,c] = random.sample(range(-2,5+1),k=3)\n# store as an array/list x\nx = random.sample(range(-2,5+1),k=3)\n# store as a set y\ny = set(random.sample(range(-2,5+1),k=3))\n```\n\n_Note that the upper bound of `range` is **excluded**._\n\n#### Shuffle a list `[2,4,6,8]` (e.g. to get `[6,8,4,2]`):\n\n```python\nimport random\n# in place shuffling\nx = [2,4,6,8]\nrandom.shuffle(x)\n# one liner with immutable input\nx = random.sample([2,4,6,8],k=4)\n```\n\n#### Generate a 2 x 3 matrix `A` with random integer elements from `{-2,-1,...,5}` using `numpy`:\n\n```python\nimport numpy\nA = numpy.random.randint(-2, 5, size=(2,3))\n# overwrite element A_{0,0}\nA[0,0] = 1337\n```\n\nElements are limited to numbers.\n\n#### Generate a 2 x 3 matrix `A` with random integer elements from `{-2,-1,...,5}` using `sympy`:\n\n```python\nfrom sympy import *\nA = randMatrix(2,3, min=-2, max=5, symmetric=False)\n# overwrite element A_{0,0}\nx, y = symbols('x,y')\nA[0,0] = sin(x) * cos(y)\n```\n\nElements can also be terms.\n\n#### Exclude the zero\n\nIn some cases, it may be beneficial to exclude the zero from random number generation. For example, a numerical question would be too easy to solve, if zero is drawn for a variable.\n\n`pySELL` provides a function `rangeZ` that behaves syntactically similar to `range`, but excludes the zero.\n\nExample to draw 3 random numbers `a`, `b`, `c` from `{-2,-1,1,2,3}` with replacement:\n\n```python\nimport random\n# get a single random number\na = random.choice(rangeZ(-2,3+1))\n# get 3 numbers with replacement (some of a,b,c may be equal)\n[a,b,c] = random.choices(rangeZ(-2,3+1),k=3)\n# get 3 numbers without replacement (a,b,c are distinct)\n[a,b,c] = random.sample(rangeZ(-2,3+1),k=3)\n```\n\n_Note that the result of `rangeZ` is of type `list`, while the built-in function `range` returns type `range`. This may be destructive in some cases!_\n\n## Developer Guide\n\nTo debug (or extend) the web code, first convert an input file into a json file with the `-J` option enabled, e.g. `python3 sell.py -J examples/ex1.txt`. Then `examples/ex1.json` is generated.\n\nThen start a local web server (e.g. using `python3 -m http.server 8000`) and open `web/index.html` (e.g. `localhost:8000/web/`, if your port number is 8000). The uncompressed JavaScript code in directory `web/src/` is interpreted as module.\n\nTo update `sell.py` after any change in the JavaScript code, and run `./build.sh` in order to update variable `html` in file `sell.py` as well as to rebuild the Python package.\n\nStructure of the repository:\n\n- `sell.py` mainly compiles an input file to a JSON file. The generation of HTML output files can be found at the end. HTML/CSS/JavaScript template code is inserted by `build.py`.\n- `build.py` builds and minifies the JavaScript code in path `web/src/`, inserts it into `web/index.html` and finally writes the self-contained HTML file into `sell.py`.\n- `docs/` contains the logo, as well as the [showcase](`https://andreas-schwenk.github.io/pysell/ex1.html`)\n- `examples/` contains example quizzes.\n- `web/` contains the front end, i.e. HTML/CSS/JavaScript code.\n- `web/index.html` is (a) used for testing; in this case, JavaScript code in path `web/src/` is loaded as module (b) used as template code for the final HTML insertion into `sell.py`\n- `web/build.js` is called by `build.py`. It uses `esbuild` to build and minify JavaScript code in path `web/src/`. Alternative build tools should also work without issues.\n\n### Dev Notes for pySELL-Maintainers\n\nUpdate as follows:\n\n1. change the version number in `pyproject.toml`\n2. update `CHANGELOG`\n3. run `./build.sh`\n4. run `twine upload dist/*`\n5. commit the code and create a new release version for `https://github.com/andreas-schwenk/pysell/releases`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreas-schwenk%2Fpysell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreas-schwenk%2Fpysell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreas-schwenk%2Fpysell/lists"}