Files
insertr/internal/content/enhancer.go
Joakim 38c2897ece Refactor configuration system with centralized type-safe config package
- Create internal/config package with unified config structs and validation
- Abstract viper dependency behind config.Loader interface for better testability
- Replace manual config parsing and type assertions with type-safe loading
- Consolidate AuthConfig, SiteConfig, and DiscoveryConfig into single package
- Add comprehensive validation with clear error messages
- Remove ~200 lines of duplicate config handling code
- Maintain backward compatibility with existing config files
2025-10-08 17:58:03 +02:00

280 lines
7.8 KiB
Go

package content
import (
"fmt"
"golang.org/x/net/html"
"os"
"path/filepath"
"strings"
"github.com/insertr/insertr/internal/config"
"github.com/insertr/insertr/internal/engine"
)
// EnhancementConfig configures the enhancement pipeline
type EnhancementConfig struct {
Discovery DiscoveryConfig
ContentInjection bool
GenerateIDs bool
}
// Type alias for backward compatibility
type DiscoveryConfig = config.DiscoveryConfig
// Enhancer combines discovery, ID generation, and content injection in unified pipeline
type Enhancer struct {
engine *engine.ContentEngine
discoverer *Discoverer
config EnhancementConfig
siteID string
}
// NewEnhancer creates a new HTML enhancer with unified pipeline
func NewEnhancer(client engine.ContentClient, siteID string, config EnhancementConfig) *Enhancer {
return &Enhancer{
engine: engine.NewContentEngine(client),
discoverer: NewDiscoverer(),
config: config,
siteID: siteID,
}
}
// NewEnhancerWithAuth creates a new HTML enhancer with auth provider
func NewEnhancerWithAuth(client engine.ContentClient, siteID string, config EnhancementConfig, authProvider *engine.AuthProvider) *Enhancer {
return &Enhancer{
engine: engine.NewContentEngineWithAuth(client, authProvider),
discoverer: NewDiscoverer(),
config: config,
siteID: siteID,
}
}
// NewDefaultEnhancer creates an enhancer with default configuration
func NewDefaultEnhancer(client engine.ContentClient, siteID string) *Enhancer {
defaultConfig := EnhancementConfig{
Discovery: DiscoveryConfig{
Enabled: true,
Aggressive: false,
Containers: true,
Individual: true,
},
ContentInjection: true,
GenerateIDs: true,
}
return NewEnhancer(client, siteID, defaultConfig)
}
// EnhanceFile processes a single HTML file through the complete pipeline
func (e *Enhancer) EnhanceFile(inputPath, outputPath string) error {
// Read HTML file
htmlContent, err := os.ReadFile(inputPath)
if err != nil {
return fmt.Errorf("reading file %s: %w", inputPath, err)
}
// Process through unified pipeline
processedHTML, err := e.processHTML(htmlContent, filepath.Base(inputPath))
if err != nil {
return fmt.Errorf("processing HTML %s: %w", inputPath, err)
}
// Create output directory
if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
return fmt.Errorf("creating output directory: %w", err)
}
// Write processed HTML
return os.WriteFile(outputPath, processedHTML, 0644)
}
// EnhanceDirectory processes all files in a directory through the unified pipeline
func (e *Enhancer) EnhanceDirectory(inputDir, outputDir string) error {
// Create output directory
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("creating output directory: %w", err)
}
// Walk input directory
return filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Calculate relative path and output path
relPath, err := filepath.Rel(inputDir, path)
if err != nil {
return err
}
outputPath := filepath.Join(outputDir, relPath)
// Handle directories
if info.IsDir() {
return os.MkdirAll(outputPath, info.Mode())
}
// Process HTML files through enhancement pipeline
if strings.HasSuffix(strings.ToLower(path), ".html") {
return e.EnhanceFile(path, outputPath)
}
// Copy non-HTML files as-is
return e.copyFile(path, outputPath)
})
}
// processHTML implements the unified enhancement pipeline
func (e *Enhancer) processHTML(htmlContent []byte, filePath string) ([]byte, error) {
var processedHTML []byte = htmlContent
// Phase 1: Element Discovery (if enabled)
if e.config.Discovery.Enabled {
discoveredHTML, err := e.discoverElements(processedHTML, filePath)
if err != nil {
return nil, fmt.Errorf("element discovery: %w", err)
}
processedHTML = discoveredHTML
}
// Phase 2 & 3: ID Generation + Content Injection (via engine)
if e.config.GenerateIDs || e.config.ContentInjection {
enhancedHTML, err := e.enhanceWithEngine(processedHTML, filePath)
if err != nil {
return nil, fmt.Errorf("engine enhancement: %w", err)
}
processedHTML = enhancedHTML
}
return processedHTML, nil
}
// discoverElements adds insertr classes to viable elements
func (e *Enhancer) discoverElements(htmlContent []byte, filePath string) ([]byte, error) {
// Parse HTML
doc, err := html.Parse(strings.NewReader(string(htmlContent)))
if err != nil {
return nil, fmt.Errorf("parsing HTML: %w", err)
}
// Find and mark viable elements
result := &FileDiscoveryResult{Document: doc}
e.discoverer.discoverNode(doc, result, e.config.Discovery.Aggressive)
// Render back to HTML
var buf strings.Builder
if err := html.Render(&buf, doc); err != nil {
return nil, fmt.Errorf("rendering HTML: %w", err)
}
return []byte(buf.String()), nil
}
// enhanceWithEngine uses the unified engine for ID generation and content injection
func (e *Enhancer) enhanceWithEngine(htmlContent []byte, filePath string) ([]byte, error) {
// Determine processing mode
var mode engine.ProcessMode
if e.config.ContentInjection {
mode = engine.Enhancement // ID generation + content injection
} else {
mode = engine.IDGeneration // ID generation only
}
// Process with engine
result, err := e.engine.ProcessContent(engine.ContentInput{
HTML: htmlContent,
FilePath: filePath,
SiteID: e.siteID,
Mode: mode,
})
if err != nil {
return nil, fmt.Errorf("engine processing: %w", err)
}
// Render enhanced document
var buf strings.Builder
if err := html.Render(&buf, result.Document); err != nil {
return nil, fmt.Errorf("rendering enhanced HTML: %w", err)
}
return []byte(buf.String()), nil
}
// SetSiteID sets the site ID for the enhancer
func (e *Enhancer) SetSiteID(siteID string) {
e.siteID = siteID
}
// EnhanceInPlace performs in-place enhancement of static site files
func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
// Use the provided siteID (derivation should happen at CLI level)
e.siteID = siteID
// Use EnhanceDirectory with same input and output (in-place)
return e.EnhanceDirectory(sitePath, sitePath)
}
// DeriveOrValidateSiteID automatically derives site_id for demo paths or validates for production
func DeriveOrValidateSiteID(sitePath string, configSiteID string) string {
// Check if this is a demo path
if strings.Contains(sitePath, "/demos/") || strings.Contains(sitePath, "./demos/") {
return deriveDemoSiteID(sitePath)
}
// For non-demo paths, return the configured site_id
// Validation of non-demo site_id will be handled at the CLI level
return configSiteID
}
// deriveDemoSiteID extracts site_id from demo directory structure
func deriveDemoSiteID(sitePath string) string {
// Convert to absolute path and clean it
absPath, err := filepath.Abs(sitePath)
if err != nil {
absPath = sitePath
}
absPath = filepath.Clean(absPath)
// Flattened structure - just use directory name after demos/
// demos/default -> "default"
// demos/simple -> "simple"
// demos/dan-eden-portfolio -> "dan-eden-portfolio"
parts := strings.Split(absPath, string(filepath.Separator))
// Find the demos directory index
demosIndex := -1
for i, part := range parts {
if part == "demos" {
demosIndex = i
break
}
}
if demosIndex == -1 || demosIndex >= len(parts)-1 {
// Fallback if demos not found in path
return "default"
}
// Get the segment after demos/ and clean it
dirName := parts[demosIndex+1]
dirName = strings.TrimSuffix(dirName, "_enhanced")
return dirName
}
// copyFile copies a file from src to dst
func (e *Enhancer) copyFile(src, dst string) error {
// Create directory for destination
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
// Read source
data, err := os.ReadFile(src)
if err != nil {
return err
}
// Write destination
return os.WriteFile(dst, data, 0644)
}