Skip to content

Commit 5b3dbcd

Browse files
committed
🌅
0 parents  commit 5b3dbcd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2564
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.egg-info

LICENSE.txt

+661
Large diffs are not rendered by default.

Makefile

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.DEFAULT_GOAL := help
2+
3+
###### Development commands
4+
5+
compile-requirements: ## Compile requirements files
6+
pip-compile requirements/base.in
7+
pip-compile requirements/dev.in
8+
9+
upgrade-requirements: ## Upgrade requirements files
10+
pip-compile --upgrade requirements/base.in
11+
pip-compile --upgrade requirements/dev.in
12+
13+
test: test-lint test-unit test-types test-format ## Run all tests
14+
15+
test-lint: ## Run linting tests
16+
pylint --errors-only --enable=unused-import,unused-argument lecture tests
17+
18+
test-unit: ## Run unit tests
19+
python -m unittest discover tests
20+
21+
test-types: ## Check types with mypy
22+
mypy --ignore-missing-imports --strict lecture tests
23+
24+
test-format: ## Check code formatting
25+
black --check lecture tests
26+
27+
format: ## Auto-format code with black
28+
black lecture tests
29+
30+
isort: ## Sort imports. This target is not mandatory because the output may be incompatible with black formatting. Provided for convenience purposes.
31+
isort --skip=templates lecture tests
32+
33+
examples: example-html example-olx ## Generate examples from the markdown file
34+
.PHONY: examples
35+
36+
example-html: ## Generate HTML example from the markdown file
37+
lecture -v examples/course.md examples/course.html
38+
39+
example-olx: ## Generate OLX example from the markdown file
40+
rm -rf examples/olx/*
41+
lecture -v examples/course.md examples/olx/
42+
tar -czf examples/olx.tar.gz examples/olx
43+
44+
###### Additional commands
45+
46+
ESCAPE = 
47+
help: ## Print this help
48+
@grep -E '^([a-zA-Z_-]+:.*?## .*|######* .+)$$' Makefile \
49+
| sed 's/######* \(.*\)/@ $(ESCAPE)[1;31m\1$(ESCAPE)[0m/g' | tr '@' '\n' \
50+
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}'

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Lecture: the course authoring tool for humans
2+
3+
Lecture is a tool for writing online courses in a human-friendly format and converting them to formats that can be understood by common learning management systems ([LMS](https://en.wikipedia.org/wiki/Learning_management_system)). In particular, Lecture can convert courses from Markdown to Open edX Learning XML ([OLX](https://edx.readthedocs.io/projects/edx-open-learning-xml/)).
4+
5+
⚠ This work is still in the alpha stage! Do not report issues just yet. ⚠
6+
7+
Supported formats:
8+
9+
- [Markdown](https://daringfireball.net/projects/markdown/): with [Pandoc-flavoured](https://garrettgman.github.io/rmarkdown/authoring_pandoc_markdown.html) header attributes.
10+
- HTML 5
11+
- Open Learning XML ([OLX](https://edx.readthedocs.io/projects/edx-open-learning-xml/) from [Open edX](https://openedx.org).
12+
13+
## Installation
14+
15+
pip install git+https://github.com/overhangio/lecture
16+
17+
## Requirements
18+
19+
Conversion from and to Markdown is handled by Lecture with the help of [Pandoc](https://pandoc.org/). Thus, a recent version of Pandoc is required when working with Markdown documents. See the corresponding [installation instructions](https://pandoc.org/installing.html).
20+
21+
## Usage
22+
23+
# Markdown -> OLX
24+
lecture /path/to/course.md /path/to/olx/
25+
# OLX -> HTML
26+
lecture /path/to/olx/course/ /path/to/course.html
27+
# HTML -> OLX
28+
lecture /path/to/course.html /path/to/olx/course/
29+
...
30+
31+
When writing Markdown files, the generated documents will include non-standard (but widely recognized) [header identifiers](https://garrettgman.github.io/rmarkdown/authoring_pandoc_markdown.html#header-identifiers) to store the course unit attributes.
32+
33+
## Examples
34+
35+
Example courses are provided in the [examples](./examples) directory.
36+
37+
## Supported unit types and formats
38+
39+
For each unit type, we indicate whether reading from (R) and writing to (W) the corresponding format are supported.
40+
41+
Unit type / Format | OLX | HTML/Markdown
42+
---|---|---
43+
Top-level course | R+W | R+W
44+
Title | R+W | R+W
45+
Multiple choice question | R+W | R+W
46+
Video | R+W | R+W
47+
Multiple choice question | R+W | R+W
48+
Raw HTML | R+W | R+W
49+
iframe | |
50+
more to come... | |
51+
52+
### Notes and known limitations
53+
54+
#### OLX
55+
56+
##### Writer
57+
58+
* Multiple choice questions are always rendered as checkboxes, and not as single-choice questions.
59+
60+
## License
61+
62+
This work is licensed under the terms of the [GNU Affero General Public License (AGPL)](https://github.com/overhangio/lecture/blob/master/LICENSE.txt).

examples/course.html

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
body {
6+
max-width: 1024px;
7+
margin: auto;
8+
font-family: sans-serif;
9+
}
10+
11+
video {
12+
width: 800px;
13+
}
14+
15+
iframe {
16+
width: 800px;
17+
height: 450px;
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
<h1 data-olx-course="lecture" data-olx-org="overhangio" data-olx-url_name="session1">
23+
My course title
24+
</h1>
25+
<h2>
26+
Chapter 1
27+
</h2>
28+
<h3>
29+
Sequential 1.1
30+
</h3>
31+
<h4>
32+
Syllabus
33+
</h4>
34+
<h5>
35+
Video
36+
</h5>
37+
<video controls>
38+
<source src="https://s3.amazonaws.com/edx-course-videos/edx-edx101/EDXSPCPJSP13-H010000_100.mp4" type="video/mp4">
39+
</source>
40+
</video>
41+
<h5>
42+
Some html content
43+
</h5>
44+
<p>
45+
Each one of these paragraphs should result in a different
46+
<code>
47+
&lt;p&gt;
48+
</code>
49+
tag.
50+
</p>
51+
<p>
52+
We can even include images here:
53+
</p>
54+
<p>
55+
<img src="https://www.google.com/images/logo.png"/>
56+
</p>
57+
<p>
58+
Raw code:
59+
</p>
60+
<pre><code>import base64
61+
base64.decodebytes(b'UmFjbGV0dGUgY2hlZXNlIGlzIHRoZSBiZXN0')</code></pre>
62+
<h4>
63+
Problems
64+
</h4>
65+
<h5>
66+
Multiple choice question
67+
</h5>
68+
<ul>
69+
<li>
70+
What is the answer to Life, the Universe, and Everything?
71+
</li>
72+
<li>
73+
✅ 6 x 7
74+
</li>
75+
<li>
76+
❌ 666
77+
</li>
78+
<li>
79+
❌ 0
80+
</li>
81+
<li>
82+
✅ 42
83+
</li>
84+
</ul>
85+
</body>
86+
</html>

examples/course.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# My course title {olx-org=overhangio olx-course=lecture olx-url_name=session1}
2+
3+
## Chapter 1
4+
5+
### Sequential 1.1
6+
7+
#### Syllabus
8+
9+
##### Video
10+
11+
![](https://s3.amazonaws.com/edx-course-videos/edx-edx101/EDXSPCPJSP13-H010000_100.mp4)
12+
13+
##### Some html content
14+
15+
Each one of these paragraphs should result in a different `<p>` tag.
16+
17+
We can even include images here:
18+
19+
![](https://www.google.com/images/logo.png)
20+
21+
Raw code:
22+
23+
import base64
24+
base64.decodebytes(b'UmFjbGV0dGUgY2hlZXNlIGlzIHRoZSBiZXN0')
25+
26+
#### Problems
27+
28+
##### Multiple choice question
29+
30+
* What is the answer to Life, the Universe, and Everything?
31+
* ✅ 6 x 7
32+
* ❌ 666
33+
* ❌ 0
34+
* ✅ 42

examples/olx.tar.gz

1.36 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<chapter display_name="Chapter 1">
2+
<sequential url_name="5e20663dadd1e483ac628951dd582ea8">
3+
</sequential>
4+
</chapter>

examples/olx/course.xml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<course course="lecture" org="overhangio" url_name="session1">
2+
</course>

examples/olx/course/session1.xml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<course display_name="My course title">
2+
<chapter url_name="cfcd208495d565ef66e7dff9f98764da">
3+
</chapter>
4+
</course>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<p>
2+
Each one of these paragraphs should result in a different
3+
<code>
4+
&lt;p&gt;
5+
</code>
6+
tag.
7+
</p>
8+
<p>
9+
We can even include images here:
10+
</p>
11+
<p>
12+
<img src="https://www.google.com/images/logo.png"/>
13+
</p>
14+
<p>
15+
Raw code:
16+
</p>
17+
<pre><code>import base64
18+
base64.decodebytes(b'UmFjbGV0dGUgY2hlZXNlIGlzIHRoZSBiZXN0')</code></pre>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<html display_name="Some html content" filename="9c65caf2f8c413fca5e46e452c6e0513">
2+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<problem display_name="Multiple choice question">
2+
<choiceresponse>
3+
<label>
4+
What is the answer to Life, the Universe, and Everything?
5+
</label>
6+
<checkboxgroup>
7+
<choice correct="true" name="0">
8+
6 x 7
9+
</choice>
10+
<choice correct="false" name="1">
11+
666
12+
</choice>
13+
<choice correct="false" name="2">
14+
0
15+
</choice>
16+
<choice correct="true" name="3">
17+
42
18+
</choice>
19+
</checkboxgroup>
20+
</choiceresponse>
21+
</problem>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<sequential display_name="Sequential 1.1">
2+
<vertical url_name="6fff48e865c3fb473f40f1d2305559f4">
3+
</vertical>
4+
<vertical url_name="936721dab1ecfa97632df0cfccd801f4">
5+
</vertical>
6+
</sequential>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<vertical display_name="Syllabus">
2+
<video url_name="e9481ee82ca9da2675e61672738bcd16">
3+
</video>
4+
<html url_name="9c65caf2f8c413fca5e46e452c6e0513">
5+
</html>
6+
</vertical>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<vertical display_name="Problems">
2+
<problem url_name="10ff7ecaac11b3716fca0067f7c8b121">
3+
</problem>
4+
</vertical>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<video display_name="Video">
2+
<source src="https://s3.amazonaws.com/edx-course-videos/edx-edx101/EDXSPCPJSP13-H010000_100.mp4">
3+
</source>
4+
</video>

lecture/__init__.py

Whitespace-only changes.

lecture/exceptions.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class LectureError(Exception):
2+
pass

lecture/formats/__init__.py

Whitespace-only changes.

lecture/formats/base/__init__.py

Whitespace-only changes.

lecture/formats/base/reader.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import typing as t
2+
3+
from lecture import units
4+
from lecture.exceptions import LectureError
5+
6+
7+
class Reader:
8+
"""
9+
TODO document me
10+
"""
11+
12+
def read(self) -> units.Course:
13+
for course in self.parse():
14+
if not isinstance(course, units.Course):
15+
# TODO better message
16+
raise LectureError(
17+
f"Failed to parse course. Expected Course object, got {course.__class__}"
18+
)
19+
return course
20+
# TODO what if there are multiple courses found?
21+
raise LectureError("No course found")
22+
23+
def parse(self) -> t.Iterable[units.Unit]:
24+
raise NotImplementedError
25+
26+
def dispatch(
27+
self, name: str, *args: t.Any, **kwargs: t.Any
28+
) -> t.Iterable[units.Unit]:
29+
# Parse element itself
30+
on_func: t.Optional[t.Callable[[t.Any], t.Iterable[units.Unit]]] = getattr(
31+
self, f"on_{name}", None
32+
)
33+
if on_func is not None:
34+
yield from on_func(*args, **kwargs)

lecture/formats/base/writer.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from lecture import units
2+
3+
4+
class Writer:
5+
def write_to(self, path: str) -> None:
6+
raise NotImplementedError
7+
8+
def write(self, unit: units.Unit) -> None:
9+
# Get the "on_<Name>" function that corresponds to the unit type.
10+
on_func = getattr(self, f"on_{unit.__class__.__name__.lower()}")
11+
on_func(unit)
12+
13+
# Write children recursively: depth-first traversal
14+
for child in unit.children:
15+
self.write(child)
16+
17+
def on_unit(self, unit: units.Unit) -> None:
18+
pass
19+
20+
def on_course(self, unit: units.Course) -> None:
21+
pass
22+
23+
def on_multiplechoicequestion(self, unit: units.MultipleChoiceQuestion) -> None:
24+
pass
25+
26+
def on_video(self, unit: units.Video) -> None:
27+
pass
28+
29+
def on_rawhtml(self, unit: units.RawHtml) -> None:
30+
pass

lecture/formats/html/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)