Merge branch 'main' of https://github.com/frappe/community into main
This commit is contained in:
@@ -8,7 +8,8 @@
|
||||
"type",
|
||||
"contents",
|
||||
"code",
|
||||
"attrs"
|
||||
"attrs",
|
||||
"index"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -37,12 +38,17 @@
|
||||
"fieldname": "attrs",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "attrs"
|
||||
},
|
||||
{
|
||||
"fieldname": "index",
|
||||
"fieldtype": "Int",
|
||||
"label": "Index"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-05 17:12:57.997046",
|
||||
"modified": "2021-03-12 17:56:23.118854",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Section",
|
||||
|
||||
@@ -7,4 +7,5 @@ from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LMSSection(Document):
|
||||
pass
|
||||
def __repr__(self):
|
||||
return f"<LMSSection {self.label!r}>"
|
||||
|
||||
@@ -3,8 +3,22 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from .section_parser import SectionParser
|
||||
|
||||
class LMSTopic(Document):
|
||||
pass
|
||||
def before_save(self):
|
||||
sections = SectionParser().parse(self.description)
|
||||
self.sections = [self.make_lms_section(i, s) for i, s in enumerate(sections)]
|
||||
|
||||
def get_sections(self):
|
||||
return sorted(self.sections, key=lambda s: s.index)
|
||||
|
||||
def make_lms_section(self, index, section):
|
||||
s = frappe.new_doc('LMS Section', parent_doc=self, parentfield='sections')
|
||||
s.type = section.type
|
||||
s.label = section.label
|
||||
s.contents = section.contents
|
||||
s.index = index
|
||||
return s
|
||||
|
||||
79
community/lms/doctype/lms_topic/section_parser.py
Normal file
79
community/lms/doctype/lms_topic/section_parser.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Utility to split the text in the topic into multiple sections.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
from typing import List, Tuple, Dict, Iterator
|
||||
|
||||
RE_SECTION = re.compile(r"^\{\{\s(\w+)\s*(?:\((.*)\))?\s*\}\}\s*")
|
||||
class SectionParser:
|
||||
def parse(self, text: str) -> Iterator[Section]:
|
||||
"""Parses given text into sections and return an iterator over sections.
|
||||
"""
|
||||
lines = text.splitlines()
|
||||
marked_lines = self.parse_lines(lines)
|
||||
return self.group_sections(marked_lines)
|
||||
|
||||
def parse_lines(self, lines: List[str]) -> List[Tuple[str, str, str]]:
|
||||
for line in lines:
|
||||
m = RE_SECTION.match(line)
|
||||
if m:
|
||||
yield m.group(1), self.parse_attrs(m.group(2)), None
|
||||
else:
|
||||
yield None, None, line
|
||||
|
||||
def parse_attrs(self, attrs_str: str) -> Dict[str, str]:
|
||||
# XXX-Anand: Hack
|
||||
code = "dict({})".format(attrs_str or "")
|
||||
return eval(code)
|
||||
|
||||
def group_sections(self, marked_lines) -> Iterator[Section]:
|
||||
index = 0
|
||||
|
||||
def make_section(type='text', id=None, label=None, **attrs):
|
||||
nonlocal index
|
||||
index += 1
|
||||
|
||||
id = id or f"section-{index}"
|
||||
label = label or id
|
||||
return Section(
|
||||
type=type,
|
||||
id=id,
|
||||
label=label,
|
||||
attrs=attrs)
|
||||
|
||||
section = make_section("text")
|
||||
|
||||
for mark, attrs, line in marked_lines:
|
||||
if not mark:
|
||||
section.append(line)
|
||||
continue
|
||||
|
||||
yield section
|
||||
|
||||
if mark == 'end':
|
||||
section = make_section(type='text')
|
||||
else:
|
||||
section = make_section(**attrs)
|
||||
|
||||
yield section
|
||||
|
||||
@dataclass
|
||||
class Section:
|
||||
"""One section of the Topic.
|
||||
"""
|
||||
type: str
|
||||
id: str
|
||||
label: str
|
||||
contents: str = ""
|
||||
attrs: dict = None
|
||||
|
||||
def append(self, line):
|
||||
if not line.endswith("\n"):
|
||||
line = line + "\n"
|
||||
self.contents += line
|
||||
|
||||
def __repr__(self):
|
||||
attrs = dict(type=self.type, id=self.id, label=self.label, **self.attrs)
|
||||
attrs_str = ", ".join(f'{k}="{v}"' for k, v in attrs.items())
|
||||
return f'<Section({attrs_str})>'
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<h1>{{ topic.title }}</h1>
|
||||
|
||||
{% for s in topic.sections %}
|
||||
{% for s in topic.get_sections() %}
|
||||
<div class="section section-{{ s.type }}">
|
||||
{{ render_section(s) }}
|
||||
</div>
|
||||
@@ -43,8 +43,8 @@
|
||||
{% macro render_section(s) %}
|
||||
{% if s.type == "text" %}
|
||||
{{ render_section_text(s) }}
|
||||
{% elif s.type == "code" %}
|
||||
{{ LiveCodeEditor(s.name, s.code) }}
|
||||
{% elif s.type == "example" or s.type == "code" %}
|
||||
{{ LiveCodeEditor(s.name, s.contents) }}
|
||||
{% else %}
|
||||
<div>Unknown section type: {{s.type}}</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user