Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/code.py: 45%

48 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-25 15:02 +0000

1import ast 

2import jinja2 

3import logics 

4from viur.core.bones.raw import RawBone 

5 

6 

7class CodeBone(RawBone): 

8 """ 

9 Stores source code with optional language-specific syntax validation. 

10 

11 The ``syntax`` parameter sets the type suffix used by the frontend for syntax 

12 highlighting, e.g. ``syntax="python"`` yields ``type = "raw.code.python"``. 

13 ``type_suffix`` can override this to an arbitrary suffix. 

14 

15 Neither ``multiple`` nor ``languages`` are supported. 

16 Setting ``validate=False`` disables any syntax validation in subclasses. 

17 """ 

18 

19 type = "raw.code" 

20 

21 def __init__( 

22 self, 

23 *, 

24 indexed: bool = False, 

25 languages=None, 

26 multiple: bool = False, 

27 syntax: str | None = None, 

28 type_suffix: str = "", 

29 validate: bool = True, 

30 **kwargs, 

31 ): 

32 assert not multiple, "CodeBone does not support multiple values" 

33 assert not languages, "CodeBone does not support language variants" 

34 self.syntax = syntax 

35 self.validate = validate 

36 if self.syntax: 36 ↛ 38line 36 didn't jump to line 38 because the condition on line 36 was always true

37 type_suffix = type_suffix or syntax 

38 super().__init__(indexed=indexed, type_suffix=type_suffix, **kwargs) 

39 

40 

41class LogicsBone(CodeBone): 

42 """ 

43 Validates its value as a Logics expression (https://github.com/viur-framework/logics). 

44 Uses Python syntax highlighting in the frontend. 

45 """ 

46 

47 def __init__(self, *, syntax: str = "logics", **kwargs): 

48 super().__init__(syntax=syntax, type_suffix="python", **kwargs) 

49 

50 def singleValueFromClient(self, value, skel, bone_name, client_data): 

51 if value := str(value or "").strip(): 

52 value += "\n" 

53 return super().singleValueFromClient(value, skel, bone_name, client_data) 

54 

55 def isInvalid(self, value): 

56 if self.validate and value: 

57 try: 

58 logics.Logics(value) 

59 except logics.ParseException as e: 

60 return str(e).replace("&eof", "end-of-expression") 

61 

62 

63class JinjaBone(CodeBone): 

64 """ 

65 Validates its value as a Jinja2 template. 

66 """ 

67 

68 def __init__(self, *, syntax: str = "jinja2", **kwargs): 

69 super().__init__(syntax=syntax, **kwargs) 

70 

71 def isInvalid(self, value): 

72 if self.validate and value: 

73 env = jinja2.Environment() 

74 try: 

75 env.parse(value) 

76 except jinja2.TemplateSyntaxError as e: 

77 return f"Syntax error in line {e.lineno}: {e.message}" 

78 except jinja2.TemplateError as e: 

79 return f"General error: {e}" 

80 

81 

82class PythonBone(CodeBone): 

83 """ 

84 Validates its value as Python source code. 

85 """ 

86 

87 def __init__(self, *, syntax: str = "python", **kwargs): 

88 super().__init__(syntax=syntax, **kwargs) 

89 

90 def isInvalid(self, value): 

91 if self.validate and value: 

92 try: 

93 ast.parse(value) 

94 except SyntaxError as e: 

95 return f"Syntax error in line {e.lineno}: {e.msg}"