Coverage for gws-app/gws/plugin/template/text/__init__.py: 52%

69 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +0200

1"""Text templates. 

2 

3The ``text`` template is written in the Jump template language, like the 

4``html`` template, but has none of its custom commands (``@map``, 

5``@legend``, ``@page`` and so on) and produces text output only. The 

6output MIME type is the ``mimeOut`` of the render input, or the first 

7configured ``mimeTypes`` entry, or plain text. If a template explicitly 

8returns a :obj:`gws.Response` object, it is returned as the render result. 

9 

10Example:: 

11 

12 templates+ { 

13 subject "feature.label" 

14 type "text" 

15 text "{{name}}" 

16 } 

17""" 

18 

19from typing import Optional 

20 

21import gws 

22import gws.base.legend 

23import gws.base.template 

24import gws.gis.render 

25import gws.lib.htmlx 

26import gws.lib.mime 

27import gws.lib.osx 

28import gws.lib.pdf 

29import gws.lib.vendor.jump 

30 

31 

32@gws.ext.config.template('text') 

33class Config(gws.base.template.Config): 

34 """Jump template for plain text output.""" 

35 

36 path: Optional[gws.FilePath] 

37 """Template file.""" 

38 text: str = '' 

39 """Template source.""" 

40 

41 

42@gws.ext.props.template('text') 

43class Props(gws.base.template.Props): 

44 pass 

45 

46 

47@gws.ext.object.template('text') 

48class Object(gws.base.template.Object): 

49 """Jump template for text output.""" 

50 

51 path: str 

52 """Template file path.""" 

53 text: str 

54 """Template source.""" 

55 compiledTime: float = 0 

56 """Time of the last compilation.""" 

57 compiledFn = None 

58 """Compiled template function.""" 

59 

60 def configure(self): 

61 self.path = self.cfg('path') 

62 self.text = self.cfg('text', default='') 

63 if not self.path and not self.text: 

64 raise gws.Error('either "path" or "text" required') 

65 

66 def render(self, tri): 

67 self.notify(tri, 'begin_print') 

68 

69 engine = Engine() 

70 self.compile(engine) 

71 

72 args = self.prepare_args(tri) 

73 res = engine.call(self.compiledFn, args=args, error=self.error_handler) 

74 

75 if not isinstance(res, gws.Response): 

76 res = self.finalize(tri, res, args, engine) 

77 

78 self.notify(tri, 'end_print') 

79 return res 

80 

81 def compile(self, engine: 'Engine'): 

82 """Compile the template if needed. 

83 

84 The template file is read again if it has changed since the last 

85 compilation. With the developer option ``template.always_reload``, 

86 the template is compiled on each call; with 

87 ``template.save_compiled``, the translated source is written to a 

88 debug file. 

89 

90 Args: 

91 engine: Jump engine. 

92 """ 

93 

94 if self.path and (not self.text or gws.lib.osx.file_mtime(self.path) > self.compiledTime): 

95 self.text = gws.u.read_file(self.path) 

96 self.compiledFn = None 

97 

98 if self.root.app.developer_option('template.always_reload'): 

99 self.compiledFn = None 

100 

101 if not self.compiledFn: 

102 gws.log.debug(f'compiling {self} {self.path=}') 

103 if self.root.app.developer_option('template.save_compiled'): 

104 gws.u.write_debug_file(f'compiled_template_{self.uid}', engine.translate(self.text, path=self.path)) 

105 

106 self.compiledFn = engine.compile(self.text, path=self.path) 

107 self.compiledTime = gws.u.utime() 

108 

109 def error_handler(self, exc, path, line, env): 

110 """Handle a template runtime error. 

111 

112 The error is logged. With the developer option 

113 ``template.raise_errors``, the error is raised, otherwise rendering 

114 continues. 

115 

116 Args: 

117 exc: The exception. 

118 path: Template path. 

119 line: Template line. 

120 env: Template environment. 

121 

122 Returns: 

123 ``True`` to continue rendering, ``False`` to raise the error. 

124 """ 

125 if self.root.app.developer_option('template.raise_errors'): 

126 gws.log.error(f'TEMPLATE_ERROR: {self}: {exc} IN {path}:{line}') 

127 return False 

128 

129 gws.log.warning(f'TEMPLATE_ERROR: {self}: {exc} IN {path}:{line}') 

130 return True 

131 

132 ## 

133 

134 def finalize(self, tri: gws.TemplateRenderInput, res: str, args: dict, main_engine: 'Engine'): 

135 """Wrap the generated text in a content response. 

136 

137 Args: 

138 tri: Template render input. 

139 res: Generated text. 

140 args: Template arguments. 

141 main_engine: Engine that rendered the text. 

142 

143 Returns: 

144 Content response. 

145 """ 

146 self.notify(tri, 'finalize_print') 

147 

148 mime_type = tri.mimeOut 

149 if not mime_type and self.mimeTypes: 

150 mime_type = self.mimeTypes[0] 

151 if not mime_type: 

152 mime_type = gws.lib.mime.TXT 

153 

154 return gws.ContentResponse(mimeType=mime_type, content=res) 

155 

156 

157## 

158 

159 

160class Engine(gws.lib.vendor.jump.Engine): 

161 """Jump engine for text templates, without custom commands.""" 

162 

163 pass