Coverage for e2xgrader/exporters/exporter.py: 54%

81 statements  

« prev     ^ index     » next       coverage.py v7.4.2, created at 2024-03-14 13:22 +0100

1import base64 

2import glob 

3import os 

4import os.path 

5 

6from bs4 import BeautifulSoup 

7from jinja2.filters import pass_context 

8from nbconvert.exporters import HTMLExporter 

9from nbgrader.server_extensions.formgrader import handlers as nbgrader_handlers 

10from traitlets import Unicode 

11 

12from ..utils import extra_cells as utils 

13from .filters import Highlight2HTMLwithLineNumbers 

14 

15 

16class E2xExporter(HTMLExporter): 

17 """ 

18 Custom E2x notebook exporter 

19 """ 

20 

21 extra_cell_field = Unicode( 

22 "extended_cell", help="The name of the extra cell metadata field." 

23 ) 

24 

25 def __init__(self, **kwargs): 

26 super().__init__(**kwargs) 

27 self.extra_template_basedirs = [ 

28 os.path.abspath( 

29 os.path.join( 

30 os.path.dirname(__file__), 

31 "..", 

32 "server_extensions", 

33 "apps", 

34 "e2xgraderapi", 

35 "templates", 

36 ) 

37 ), 

38 os.path.abspath( 

39 os.path.join( 

40 os.path.dirname(__file__), 

41 "..", 

42 "server_extensions", 

43 "apps", 

44 "formgrader", 

45 "templates", 

46 ) 

47 ), 

48 nbgrader_handlers.template_path, 

49 ] 

50 # The notebook seems to sometimes set exclude_input to true 

51 self.exclude_input = False 

52 

53 @property 

54 def template_paths(self): 

55 return super()._template_paths() + [ 

56 os.path.abspath( 

57 os.path.join( 

58 os.path.dirname(__file__), 

59 "..", 

60 "server_extensions", 

61 "grader", 

62 "apps", 

63 "formgrader", 

64 "templates", 

65 ) 

66 ) 

67 ] 

68 

69 @pass_context 

70 def to_choicecell(self, context, source): 

71 cell = context.get("cell", {}) 

72 soup = BeautifulSoup(source, "html.parser") 

73 my_type = None 

74 if not soup.ul or not utils.is_extra_cell(cell): 

75 return soup.prettify().replace("\n", "") 

76 if utils.is_singlechoice(cell): 

77 my_type = "radio" 

78 elif utils.is_multiplechoice(cell): 

79 my_type = "checkbox" 

80 form = soup.new_tag("form") 

81 form["class"] = "hbrs_checkbox" 

82 

83 list_elems = soup.ul.find_all("li") 

84 for i in range(len(list_elems)): 

85 div = soup.new_tag("div") 

86 box = soup.new_tag("input") 

87 box["type"] = my_type 

88 box["value"] = i 

89 box["disabled"] = "disabled" 

90 if i in utils.get_choices(cell): 

91 box["checked"] = "checked" 

92 div.append(box) 

93 children = [c for c in list_elems[i].children] 

94 for child in children: 

95 div.append(child) 

96 

97 if utils.has_solution(cell): 

98 check = soup.new_tag("span") 

99 if i in utils.get_instructor_choices(cell): 

100 check.string = "correct" 

101 check["style"] = "color:green" 

102 else: 

103 check.string = "false" 

104 check["style"] = "color:red" 

105 div.append(check) 

106 form.append(div) 

107 soup.ul.replaceWith(form) 

108 return soup.prettify().replace("\n", "") 

109 

110 def default_filters(self): 

111 for pair in super(E2xExporter, self).default_filters(): 

112 yield pair 

113 yield ("to_choicecell", self.to_choicecell) 

114 

115 def discover_annotations(self, resources): 

116 if resources is None: 

117 return 

118 resources["annotations"] = dict() 

119 if "metadata" not in resources or "path" not in resources["metadata"]: 

120 return 

121 

122 path = resources["metadata"]["path"] 

123 

124 for annotation in glob.glob(os.path.join(path, "annotations", "*.png")): 

125 cell_id = os.path.splitext(os.path.basename(annotation))[0] 

126 with open(annotation, "rb") as f: 

127 img = base64.b64encode(f.read()).decode("utf-8") 

128 resources["annotations"][cell_id] = f"data:image/png;base64,{img}" 

129 

130 def from_notebook_node(self, nb, resources=None, **kw): 

131 self.discover_annotations(resources) 

132 

133 self.exclude_input = False 

134 langinfo = nb.metadata.get("language_info", {}) 

135 lexer = langinfo.get("pygments_lexer", langinfo.get("name", None)) 

136 highlight_code = self.filters.get( 

137 "highlight_code_with_linenumbers", 

138 Highlight2HTMLwithLineNumbers(pygments_lexer=lexer, parent=self), 

139 ) 

140 self.register_filter("highlight_code_with_linenumbers", highlight_code) 

141 return super(E2xExporter, self).from_notebook_node(nb, resources, **kw)