Coverage for src/jsoncrack_for_sphinx/core/directive.py: 77%

74 statements  

« prev     ^ index     » next       coverage.py v7.10.0, created at 2025-07-24 22:26 +0000

1""" 

2Sphinx directive for manual schema inclusion. 

3""" 

4 

5import re 

6from pathlib import Path 

7from typing import List, Optional 

8 

9from docutils import nodes 

10from docutils.parsers.rst import directives 

11from sphinx.util import logging 

12from sphinx.util.docutils import SphinxDirective 

13 

14from ..config import get_config_values 

15from ..config.config_utils import get_jsoncrack_config 

16from ..generators.html_generator import generate_schema_html 

17 

18logger = logging.getLogger(__name__) 

19 

20 

21class SchemaDirective(SphinxDirective): 

22 """Directive to manually include a schema in documentation.""" 

23 

24 has_content = False 

25 required_arguments = 1 

26 optional_arguments = 0 

27 option_spec = { 

28 "title": directives.unchanged, 

29 "description": directives.unchanged, 

30 "render_mode": directives.unchanged, 

31 "theme": directives.unchanged, 

32 "direction": directives.unchanged, 

33 "height": directives.unchanged, 

34 "width": directives.unchanged, 

35 "onscreen_threshold": directives.unchanged, 

36 "onscreen_margin": directives.unchanged, 

37 } 

38 

39 def run(self) -> List[nodes.Node]: 

40 """Process the schema directive.""" 

41 schema_name = self.arguments[0] 

42 config = self.env.config 

43 

44 schema_path = self._find_schema_file(schema_name, config.json_schema_dir) 

45 if not schema_path: 

46 logger.warning(f"Schema file not found: {schema_name}") 

47 return [] 

48 

49 try: 

50 html_content = self._generate_schema_html(schema_path) 

51 return [nodes.raw("", html_content, format="html")] 

52 except Exception as e: 

53 logger.error(f"Error generating schema HTML: {e}") 

54 return [] 

55 

56 def _find_schema_file(self, schema_name: str, schema_dir: str) -> Optional[Path]: 

57 """Find schema file by name.""" 

58 if not schema_dir: 

59 return None 

60 

61 schema_dir_path = Path(schema_dir) 

62 if not schema_dir_path.exists(): 

63 return None 

64 

65 # Try different file patterns 

66 patterns = [ 

67 f"{schema_name}.schema.json", 

68 f"{schema_name}.json", 

69 ] 

70 

71 for pattern in patterns: 

72 schema_path = schema_dir_path / pattern 

73 if schema_path.exists(): 

74 return schema_path 

75 

76 return None 

77 

78 def _generate_schema_html(self, schema_path: Path) -> str: 

79 """Generate HTML for JSONCrack visualization of a schema file.""" 

80 # Determine file type based on filename 

81 file_type = ( 

82 "schema" 

83 if schema_path.suffix == ".json" and ".schema." in schema_path.name 

84 else "json" 

85 ) 

86 

87 # Generate HTML using the same logic as html_generator 

88 html_content = generate_schema_html(schema_path, file_type, self.env.config) 

89 

90 # Apply directive options to the generated HTML 

91 config = self.env.config 

92 jsoncrack_config = get_jsoncrack_config(config) 

93 config_values = get_config_values(jsoncrack_config) 

94 

95 # Override with directive options if provided 

96 if "render_mode" in self.options: 

97 config_values["render_mode"] = self.options["render_mode"] 

98 if "theme" in self.options: 

99 config_values["theme"] = self.options["theme"] 

100 if "direction" in self.options: 

101 config_values["direction"] = self.options["direction"] 

102 if "height" in self.options: 

103 config_values["height"] = self.options["height"] 

104 if "width" in self.options: 

105 config_values["width"] = self.options["width"] 

106 if "onscreen_threshold" in self.options: 

107 config_values["onscreen_threshold"] = self.options["onscreen_threshold"] 

108 if "onscreen_margin" in self.options: 

109 config_values["onscreen_margin"] = self.options["onscreen_margin"] 

110 

111 # Update data attributes in the HTML with directive options 

112 for key, value in config_values.items(): 

113 if key in [ 

114 "render_mode", 

115 "theme", 

116 "direction", 

117 "height", 

118 "width", 

119 "onscreen_threshold", 

120 "onscreen_margin", 

121 ]: 

122 pattern = f'data-{key.replace("_", "-")}="[^"]*"' 

123 replacement = f'data-{key.replace("_", "-")}="{value}"' 

124 html_content = re.sub(pattern, replacement, html_content) 

125 

126 # Add title and description if provided 

127 if "title" in self.options or "description" in self.options: 

128 title = self.options.get("title", "") 

129 description = self.options.get("description", "") 

130 

131 if title: 

132 html_content = f"<h3>{title}</h3>" + html_content 

133 if description: 

134 html_content = f"<p>{description}</p>" + html_content 

135 

136 return html_content