syntax * - Component references (PascalCase tags) * - Dependencies (@import directives) * - Template metadata for component binding */ class Jqhtml_ManifestModule extends ManifestModule_Abstract { /** * Get file extensions this module handles * * @return array */ public function handles(): array { return ['jqhtml', 'jqtpl']; } /** * Get processing priority (lower = earlier) * * @return int */ public function priority(): int { return 500; // Process after core types but before CSS } /** * Process a file and extract metadata * * @param string $file_path Full path to the file * @param array $metadata Existing metadata * @return array Updated metadata */ public function process(string $file_path, array $metadata): array { // Read template content $content = file_get_contents($file_path); // Check if file has meaningful content after comment removal $cleaned_content = $this->remove_comments($content); $trimmed_content = trim($cleaned_content); // If file is effectively empty after comments removed and trimmed, don't require a template name if (empty($trimmed_content)) { $metadata['type'] = 'jqhtml_template'; $metadata['components'] = []; $metadata['dependencies'] = []; $metadata['slots'] = []; return $metadata; } // Extract template name $template_name = $this->extract_template_name($content); if (!$template_name) { // Non-empty file must have a template name - fail loud throw new \RuntimeException("JQHTML template in {$file_path} has content but no tag. Please add a template definition to the file."); } // Extract component references $components = $this->extract_components($content); // Extract dependencies $dependencies = $this->extract_dependencies($content); // Extract slots $slots = $this->extract_slots($content); // Extract tag name (defaults to 'div' if not specified) $tag_name = $this->extract_tag_name($content); // Add JQHTML-specific metadata $metadata['template_name'] = $template_name; $metadata['id'] = $template_name; // Set the ID to the component name $metadata['type'] = 'jqhtml_template'; $metadata['tag_name'] = $tag_name; $metadata['components'] = $components; $metadata['dependencies'] = $dependencies; $metadata['slots'] = $slots; // Check if this template has a matching ES6 class $class_name = $this->to_class_name($template_name); $metadata['expected_class'] = $class_name; return $metadata; } /** * Remove comments from JQHTML content * * @param string $content Template content * @return string Content with comments removed */ protected function remove_comments(string $content): string { // Remove <%-- --%> style comments $content = preg_replace('/<%--.*?--%>/s', '', $content); // Remove style comments $content = preg_replace('//s', '', $content); return $content; } /** * Extract template name from content * * @param string $content Template content * @return string|null Template name */ protected function extract_template_name(string $content): ?string { // Remove comments before searching for Define: $content = $this->remove_comments($content); // Find the first instance of remove_comments($content); // Look for tag attribute in Define statement // Matches: or if (preg_match('/]*tag=["\']([^"\']+)["\']/', $content, $matches)) { return $matches[1]; } return 'div'; // Default tag name } /** * Extract component references from template * * @param string $content Template content * @return array Component names */ protected function extract_components(string $content): array { $components = []; // Match PascalCase tags (likely components) preg_match_all('/<([A-Z][a-zA-Z0-9_]*)(?:\s+[^>]*)?\/?>|<\/([A-Z][a-zA-Z0-9_]*)>/', $content, $matches); foreach ($matches[1] as $tag) { if ($tag && !in_array($tag, $components)) { $components[] = $tag; } } foreach ($matches[2] as $tag) { if ($tag && !in_array($tag, $components)) { $components[] = $tag; } } // Remove Define tags as they're not component references $components = array_filter($components, function($c) { return !str_starts_with($c, 'Define'); }); return array_values($components); } /** * Extract dependencies from template * * @param string $content Template content * @return array Dependency paths */ protected function extract_dependencies(string $content): array { $dependencies = []; // Match @import directives preg_match_all('/@import\s+[\'"]([^\'"]+)[\'"]/', $content, $matches); foreach ($matches[1] as $dep) { if ($dep && !in_array($dep, $dependencies)) { $dependencies[] = $dep; } } return $dependencies; } /** * Extract slot definitions from template * * @param string $content Template content * @return array Slot names */ protected function extract_slots(string $content): array { $slots = []; // Match syntax preg_match_all('//', $content, $matches); foreach ($matches[1] as $slot) { if ($slot && !in_array($slot, $slots)) { $slots[] = $slot; } } // Also match syntax preg_match_all('/