From 6abc66c68e4de56aad289dc3134dde5827de1c73 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Sun, 30 Jun 2024 15:07:42 +0200
Subject: [PATCH] fix: exluding now work properly

---
 source/html/sync.go                | 231 +++++++++++++++++++----------
 source/types/sync-specification.go |  14 +-
 2 files changed, 160 insertions(+), 85 deletions(-)

diff --git a/source/html/sync.go b/source/html/sync.go
index 88237fb..2e64cc7 100644
--- a/source/html/sync.go
+++ b/source/html/sync.go
@@ -26,63 +26,51 @@ func readHTML(p string) (*html.Node, error) {
 	return html.Parse(htmlFile)
 }
 
-func SyncHtml(p string) error {
-
-	content, err := os.ReadFile(p)
-	if err != nil {
-		return err
-	}
-
-	currentDir, _ := os.Getwd()
-	if err := os.Chdir(filepath.Dir(p)); err != nil {
-		return err
-	}
+type StringNodeMap map[string]*html.Node
+type StringListNodeMap map[string][]*html.Node
 
-	defer func() {
-		_ = os.Chdir(currentDir)
-	}()
-
-	specification := types.SyncSpecification{}
-
-	if err := yaml.Unmarshal(content, &specification); err != nil {
-		return err
-	}
-
-	destinationWriteFiles := make(map[string]*html.Node)
+func getSourceFileMap(specification types.SyncSpecification) (StringNodeMap, error) {
+	sourceFiles := make(StringNodeMap)
 
 	for _, r := range specification.Sync {
 
-		sourceFiles := make(map[string]*html.Node)
-		destinationFiles := make(map[string]*html.Node)
-		destinationFile := make(map[string][]string)
-
 		source := r.Source
-		destination := r.Destination
-		exclude := r.Destination.Exclude
 
 		absSource, err := filepath.Abs(source.Path)
 		if err != nil {
-			return err
+			return nil, err
 		}
 
-		if err != nil {
-			return err
+		// if already read, skip
+		if _, ok := sourceFiles[absSource]; ok {
+			continue
 		}
 
 		if sourceFiles[absSource], err = readHTML(absSource); err != nil {
-			return err
+			return nil, err
 		}
 
-		for _, d := range destination.Path {
+	}
+
+	return sourceFiles, nil
+}
+
+func getDestinationFiles(specification types.SyncSpecification) (StringNodeMap, error) {
+
+	fileMap := make(StringNodeMap)
+
+	for _, r := range specification.Sync {
+
+		for _, d := range r.Destination.Path {
 
 			d, err := filepath.Abs(d)
 			if err != nil {
-				return err
+				return nil, err
 			}
 
 			fileInfo, err := os.Stat(d)
 			if err != nil {
-				return err
+				return nil, err
 			}
 
 			if fileInfo.IsDir() {
@@ -99,12 +87,7 @@ func SyncHtml(p string) error {
 						return nil
 					}
 
-					excludeFlag, err2 := checkExcludes(exclude, pp)
-					if err2 != nil {
-						return err2
-					}
-
-					if excludeFlag {
+					if _, ok := fileMap[pp]; ok {
 						return nil
 					}
 
@@ -113,65 +96,153 @@ func SyncHtml(p string) error {
 						return err
 					}
 
-					destinationFiles[pp] = dd
-					destinationWriteFiles[pp] = dd
-					destinationFile[absSource] = append(destinationFile[absSource], pp)
+					fileMap[pp] = dd
 
 					return nil
 				}); err != nil {
-					return err
+					return nil, err
 				}
-			} else if filepath.Ext(d) == ".html" {
 
-				if exclude != nil {
-					excludeFlag, err2 := checkExcludes(exclude, d)
-					if err2 != nil {
-						return err2
-					}
+			} else if filepath.Ext(d) == ".html" {
 
-					if excludeFlag {
-						continue
-					}
+				if _, ok := fileMap[d]; ok {
+					continue
 				}
 
 				var dd *html.Node
 				if dd, err = readHTML(d); err != nil {
-					return err
+					return nil, err
 				}
 
-				destinationFiles[d] = dd
-				destinationWriteFiles[d] = dd
-				destinationFile[absSource] = append(destinationFile[absSource], d)
+				fileMap[d] = dd
 			}
 		}
 
-		//source := r.Source
-		//absSource, err := filepath.Abs(source.Path)
-		//if err != nil {
-		//	return err
-		//}
-		sourceSelector := source.Selector
+	}
 
-		query, err := cascadia.Compile(sourceSelector)
+	return fileMap, nil
+}
+
+func getAllDestinationFilesWithoutExcludes(destination types.Destination) ([]string, error) {
+	var files []string
+
+	for _, p := range destination.Path {
+		p, err := filepath.Abs(p)
+		if err != nil {
+			return nil, err
+		}
+
+		if err = filepath.Walk(p, func(pp string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			if info.IsDir() {
+				return nil
+			}
+
+			ext := filepath.Ext(pp)
+			if ext != ".html" {
+				return nil
+			}
+
+			exclude, err := checkExcludes(destination.Exclude, pp)
+			if err != nil {
+				return err
+			}
+
+			if exclude {
+				return nil
+			}
+
+			files = append(files, pp)
+
+			return nil
+		}); err != nil {
+			return nil, err
+		}
+
+	}
+
+	return files, nil
+}
+
+func SyncHtml(p string) error {
+
+	content, err := os.ReadFile(p)
+	if err != nil {
+		return err
+	}
+
+	currentDir, _ := os.Getwd()
+	if err := os.Chdir(filepath.Dir(p)); err != nil {
+		return err
+	}
+
+	defer func() {
+		_ = os.Chdir(currentDir)
+	}()
+
+	specification := types.SyncSpecification{}
+
+	if err := yaml.Unmarshal(content, &specification); err != nil {
+		return err
+	}
+
+	var sourceFiles StringNodeMap
+	sourceFiles, err = getSourceFileMap(specification)
+	if err != nil {
+		return err
+	}
+
+	var destinationFiles StringNodeMap
+	destinationFiles, err = getDestinationFiles(specification)
+	if err != nil {
+		return err
+	}
+
+	changedFiles := make(map[string]struct{})
+	for _, r := range specification.Sync {
+
+		source := r.Source
+		absSource, err := filepath.Abs(source.Path)
 		if err != nil {
 			return err
 		}
 
-		s, ok := sourceFiles[absSource]
+		sourceNode, ok := sourceFiles[absSource]
 		if !ok {
 			return fmt.Errorf("source file isn't found: %s", absSource)
 		}
 
-		sourceNode := query.MatchFirst(s)
+		sourceSelector := source.Selector
+		query, err := cascadia.Compile(sourceSelector)
+		if err != nil {
+			return err
+		}
+
+		sourceNode = query.MatchFirst(sourceNode)
+		if sourceNode == nil {
+			return fmt.Errorf("source selector not found: %s in %s", sourceSelector, absSource)
+		}
+
+		list, err := getAllDestinationFilesWithoutExcludes(r.Destination)
+		if err != nil {
+			return err
+		}
 
-		dp := destinationFile[absSource]
-		for _, d := range dp {
+		for _, d := range list {
 			destinationSelector := r.Destination.Selector
 			if destinationSelector == "" {
 				destinationSelector = sourceSelector
 			}
 
-			keepMap := make(map[string][]*html.Node)
+			query, err := cascadia.Compile(destinationSelector)
+			if err != nil {
+				return err
+			}
+
+			keepMap := make(StringListNodeMap)
 
 			for _, n := range r.Destination.Keep {
 				q, err := cascadia.Compile(n)
@@ -190,13 +261,7 @@ func SyncHtml(p string) error {
 
 			}
 
-			query, err := cascadia.Compile(destinationSelector)
-			if err != nil {
-				return err
-			}
-
 			destinationData := query.MatchFirst(destinationFiles[d])
-
 			if destinationData == nil {
 				return fmt.Errorf("could not find destination selector %s in %s", destinationSelector, d)
 			}
@@ -227,17 +292,24 @@ func SyncHtml(p string) error {
 						n1.Parent.InsertBefore(engine.CloneNode(kk), n1)
 						n1.Parent.RemoveChild(n1)
 					}
+
 				}
+
 			}
+
+			changedFiles[d] = struct{}{}
+
 		}
+
 	}
 
-	for p, d := range destinationWriteFiles {
-		fp, err := os.Create(p)
+	for d, _ := range changedFiles {
+
+		fp, err := os.Create(d)
 		if err != nil {
 			return err
 		}
-		err = html.Render(fp, d)
+		err = html.Render(fp, destinationFiles[d])
 		err2 := fp.Close()
 		if err2 != nil {
 			return err2
@@ -246,6 +318,7 @@ func SyncHtml(p string) error {
 		if err != nil {
 			return err
 		}
+
 	}
 
 	return nil
diff --git a/source/types/sync-specification.go b/source/types/sync-specification.go
index b6047c1..fc3b1fd 100644
--- a/source/types/sync-specification.go
+++ b/source/types/sync-specification.go
@@ -21,17 +21,19 @@ func (p *PathOrList) UnmarshalYAML(value *yaml.Node) error {
 	return nil
 }
 
+type Destination struct {
+	Selector string     `yaml:"selector"`
+	Path     PathOrList `yaml:"path"`
+	Exclude  []string   `yaml:"exclude"`
+	Keep     []string   `yaml:"keep"`
+}
+
 type Sync struct {
 	Source struct {
 		Selector string `yaml:"selector"`
 		Path     string `yaml:"path"`
 	} `yaml:"source"`
-	Destination struct {
-		Selector string     `yaml:"selector"`
-		Path     PathOrList `yaml:"path"`
-		Exclude  []string   `yaml:"exclude"`
-		Keep     []string   `yaml:"keep"`
-	} `yaml:"destination"`
+	Destination Destination `yaml:"destination"`
 }
 
 type SyncSpecification struct {
-- 
GitLab