Coverage for src / lexigram / ui / layouts / head.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 04:11 +0800

1"""Head section renderer. 

2 

3Renders the HTML head section with CSS and meta tags. 

4""" 

5 

6from __future__ import annotations 

7 

8from markupsafe import escape 

9 

10from lexigram.ui.config import HeadConfig 

11 

12 

13class HeadRenderer: 

14 """Renders the head section content.""" 

15 

16 CSS_FRAMEWORKS = { 

17 "tailwind": "https://cdn.tailwindcss.com", 

18 "pico": "https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css", 

19 } 

20 

21 def __init__(self, config: HeadConfig | None = None): 

22 """Initialize the renderer. 

23 

24 Args: 

25 config: Head configuration 

26 """ 

27 self.config = config or HeadConfig() 

28 

29 def render(self, extra_css: str = "") -> str: 

30 """Render the head content. 

31 

32 Args: 

33 extra_css: Additional inline CSS to include 

34 

35 Returns: 

36 HTML string for head section 

37 """ 

38 parts: list[str] = [] 

39 

40 # CSS Framework 

41 parts.append(self._render_css_framework()) 

42 

43 # Font 

44 if self.config.font_url: 

45 parts.append('<link rel="preconnect" href="https://fonts.googleapis.com">') 

46 parts.append( 

47 f'<link rel="stylesheet" href="{escape(self.config.font_url)}">', 

48 ) 

49 

50 # Icon library 

51 parts.append(self._render_icon_library()) 

52 

53 # Custom CSS files 

54 for css_file in self.config.css_files: 

55 parts.append(f'<link rel="stylesheet" href="{escape(css_file)}">') 

56 

57 # HTMX 

58 parts.append(f'<script src="{escape(self.config.htmx_url)}"></script>') 

59 

60 # Hyperscript 

61 if self.config.include_hyperscript: 

62 parts.append( 

63 f'<script src="{escape(self.config.hyperscript_url)}"></script>', 

64 ) 

65 

66 # Inline CSS 

67 if self.config.inline_css or extra_css: 

68 parts.append("<style>") 

69 if self.config.inline_css: 

70 parts.append(self.config.inline_css) 

71 if extra_css: 

72 parts.append(extra_css) 

73 parts.append("</style>") 

74 

75 return "\n".join(parts) 

76 

77 def _render_css_framework(self) -> str: 

78 """Render CSS framework link.""" 

79 if self.config.css_framework_url: 

80 url = self.config.css_framework_url 

81 else: 

82 url = self.CSS_FRAMEWORKS.get(self.config.css_framework, "") 

83 

84 if not url: 

85 return "" 

86 

87 if self.config.css_framework == "tailwind": 

88 return f'<script src="{escape(url)}"></script>' 

89 return f'<link rel="stylesheet" href="{escape(url)}">' 

90 

91 def _render_icon_library(self) -> str: 

92 """Render icon library script.""" 

93 if self.config.icon_library == "lucide" and self.config.icon_library_url: 

94 return f""" 

95<script src="{escape(self.config.icon_library_url)}"></script> 

96<script> 

97 document.addEventListener('DOMContentLoaded', function() {{ 

98 if (window.lucide) lucide.createIcons(); 

99 }}); 

100</script>""" 

101 return "" 

102 

103 

104__all__ = ["HeadConfig", "HeadRenderer"]