From e2b6401872698252a35bf9c73958668e7a2431b2 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Wed, 19 Jul 2023 23:36:31 +0200
Subject: [PATCH] feat: Keep values in existing file

---
 .attach_pid1342408                     |   0
 .attach_pid1349125                     |   0
 .gitignore                             |   1 +
 README.md                              |   8 +-
 application/source/command.go          |  75 +++++++++++++++--
 application/source/html/generate.go    |   4 +-
 application/source/html/sync.go        |  11 ---
 application/source/template/prepare.go | 111 +++++++++++++++++++++++--
 application/source/util/html.go        |  16 +++-
 devenv.lock                            |  18 ++--
 10 files changed, 200 insertions(+), 44 deletions(-)
 delete mode 100644 .attach_pid1342408
 delete mode 100644 .attach_pid1349125

diff --git a/.attach_pid1342408 b/.attach_pid1342408
deleted file mode 100644
index e69de29..0000000
diff --git a/.attach_pid1349125 b/.attach_pid1349125
deleted file mode 100644
index e69de29..0000000
diff --git a/.gitignore b/.gitignore
index a750869..c9341d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -526,3 +526,4 @@ devenv.local.nix
 # pre-commit
 .pre-commit-config.yaml
 
+.attach_pid*
diff --git a/README.md b/README.md
index f3e2486..02d1f5f 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ This repository contains a file called flake.nix. You can install this program u
 #### Prepare
 
 ```bash
-bob template prepare --input ./templates/ --output ./output/
+bob template prepare --input ./templates/ --output ./output/ --data-file ./data.yaml
 ```
 
 This will create files in the `./output/` directory with all parsed templates from `./templates/` directory.
@@ -32,13 +32,15 @@ Also, a data YAML. This data YAML is used to generate the final files.
 This command prepares the title, description, keywords, and other metadata for the templates.
 Furthermore, it will parse the templates for images, anchors, and text.
 
+If the argument `--data-file` is not set, the data YAML will be written to `./output/data.yaml`.
+
 | Original                                                                      | Parsed                                                                                                                                          |
 |-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
 | `<html lang="en"><head>`                                                      | `<html lang="en" data-attributes="lang path:lang"><head>`                                                                                       |
 | `<title>Bob</title>`                                                          | `<title data-attributes="title path:title">Bob</title>`                                                                                         |
 | `<meta name="description" content="Bob is a html and html fragment builder">` | `<meta name="description" content="Bob is a html and html fragment builder" data-attributes="description path:meta.description">`               |
-| `<img alt="alt text" title="my title" src="..." `                            | `<img alt="alt text" title="my title" src="..." data-attributes="alt path:img.id1003.alt title path:img.id1003.title src path:img.id1003.src">` |
-| `<a href="https://gitlab.schukai.com/oss/bob">`                              | `<a href="https://gitlab.schukai.com/oss/bob" data-attributes="href path:a.id1004.href">`                                                       |
+| `<img alt="alt text" title="my title" src="..." `                             | `<img alt="alt text" title="my title" src="..." data-attributes="alt path:img.id1003.alt title path:img.id1003.title src path:img.id1003.src">` |
+| `<a href="https://gitlab.schukai.com/oss/bob">`                               | `<a href="https://gitlab.schukai.com/oss/bob" data-attributes="href path:a.id1004.href">`                                                       |
 | `<p>Bob is a html and html fragment builder</p>`                              | `<p><span data-attributes="text path:p.id1005.text">Bob is a html and html fragment builder</span></p>`                                         |
 
 
diff --git a/application/source/command.go b/application/source/command.go
index 7885063..fdec0b7 100644
--- a/application/source/command.go
+++ b/application/source/command.go
@@ -82,6 +82,29 @@ func (d *Definition) PrepareTemplate(s *xflags.Settings[Definition]) {
 
 	storage := types.NewPageDataStorage()
 
+	i := d.GetDataFile()
+	// unmarshal data file yaml to page data storage
+	if _, err := os.Stat(i); err == nil {
+		data, err := os.ReadFile(i)
+		if err != nil {
+			s.AddError(err)
+		} else {
+			err = yaml.Unmarshal(data, storage)
+			if err != nil {
+				s.AddError(err)
+			}
+		}
+
+		if d.Verbose {
+			fmt.Printf("Loaded data file %s\n", i)
+		}
+	}
+
+	toDelete := []string{}
+	for page, _ := range storage {
+		toDelete = append(toDelete, page)
+	}
+
 	err := filepath.Walk(d.Template.Prepare.Input, func(path string, info os.FileInfo, err error) error {
 
 		if err != nil {
@@ -97,9 +120,34 @@ func (d *Definition) PrepareTemplate(s *xflags.Settings[Definition]) {
 			return nil
 		}
 
-		return template2.PrepareHtmlFile(path, d.Template.Prepare.Output, storage)
+		if d.Verbose {
+			fmt.Printf("Prepare %s\n", path)
+		}
+
+		key, err := template2.PrepareHtmlFile(path, d.Template.Prepare.Output, storage)
+		if err != nil {
+			return err
+		}
+
+		for i, v := range toDelete {
+			if v == key {
+				toDelete = append(toDelete[:i], toDelete[i+1:]...)
+				break
+			}
+		}
+
+		return nil
 	})
 
+	for _, v := range toDelete {
+
+		if d.Verbose {
+			fmt.Printf("Delete %s\n", v)
+		}
+
+		delete(storage, v)
+	}
+
 	if err != nil {
 		s.AddError(err)
 	}
@@ -109,22 +157,31 @@ func (d *Definition) PrepareTemplate(s *xflags.Settings[Definition]) {
 		s.AddError(err)
 	} else {
 
-		o := d.Template.Prepare.DataFile
-		if o == "" {
-			o = "data.yaml"
-		}
-
-		if !path.IsAbs(o) {
-			o = path.Join(d.Template.Prepare.Output, o)
-		}
+		o := d.GetDataFile()
 
 		err = os.WriteFile(o, data, os.ModePerm)
 		if err != nil {
 			s.AddError(err)
 		}
 
+		if d.Verbose {
+			fmt.Printf("Saved data file %s\n", o)
+		}
+
+	}
+
+}
+
+func (d *Definition) GetDataFile() string {
+	o := d.Template.Prepare.DataFile
+	if o == "" {
+		o = "data.yaml"
 	}
 
+	if !path.IsAbs(o) {
+		o = path.Join(d.Template.Prepare.Output, o)
+	}
+	return o
 }
 
 func (d *Definition) PrintHelp(s *xflags.Settings[Definition]) {
diff --git a/application/source/html/generate.go b/application/source/html/generate.go
index 016b8fe..185c10b 100644
--- a/application/source/html/generate.go
+++ b/application/source/html/generate.go
@@ -28,7 +28,7 @@ func GenerateFiles(dataPath, out string) error {
 
 	for name, page := range storage {
 		p := path.Join(dir, name)
-		html, err := Generate(page, p)
+		generatedHtml, err := Generate(page, p)
 		if err != nil {
 			return err
 		}
@@ -40,7 +40,7 @@ func GenerateFiles(dataPath, out string) error {
 			return err
 		}
 
-		err = os.WriteFile(outFile, []byte(html), 0644)
+		err = os.WriteFile(outFile, []byte(generatedHtml), 0644)
 		if err != nil {
 			return err
 		}
diff --git a/application/source/html/sync.go b/application/source/html/sync.go
index 2b36272..1784580 100644
--- a/application/source/html/sync.go
+++ b/application/source/html/sync.go
@@ -9,7 +9,6 @@ import (
 	"gopkg.in/yaml.v3"
 	"os"
 	"path/filepath"
-	"strings"
 )
 
 type Specification struct {
@@ -202,16 +201,6 @@ func SyncHtml(p string) error {
 					}
 					x := cas.MatchAll(destinationFiles[d])
 					if x == nil {
-
-						builder := &strings.Builder{}
-
-						// Render the HTML to the builder
-						html.Render(builder, destinationFiles[d])
-
-						// Print the captured HTML to the console
-						a := builder.String()
-						fmt.Println(a)
-
 						return fmt.Errorf("could not find selector %s in %s", sel, d)
 					}
 
diff --git a/application/source/template/prepare.go b/application/source/template/prepare.go
index 038bf35..fb7fd54 100644
--- a/application/source/template/prepare.go
+++ b/application/source/template/prepare.go
@@ -73,7 +73,16 @@ func setDataAttributesAttribute(node *html.Node, name, attribute, instruction st
 
 }
 
-func prepateMeta(node *html.Node, attrKey, attrValue string, storage *types.PageData) {
+func prepareMeta(node *html.Node, attrKey, attrValue string, storage *types.PageData) {
+
+	if storage.Meta == nil {
+		storage.Meta = make(map[string]string)
+	}
+
+	if _, ok := storage.Meta[attrValue]; ok {
+		return
+	}
+
 	sel, err := cascadia.Parse("meta[" + attrKey + "=" + attrValue + "]")
 	if err != nil {
 		return
@@ -115,6 +124,10 @@ func prepareLanguage(node *html.Node, storage *types.PageData) {
 
 func prepareTitle(node *html.Node, storage *types.PageData) {
 
+	if storage.Title != "" {
+		return
+	}
+
 	selector, err := cascadia.Parse("title")
 	if err != nil {
 		return
@@ -150,6 +163,10 @@ func prepareAnchors(node *html.Node, storage *types.PageData) {
 		return
 	}
 
+	copyOfAnchors := make([]types.Anchor, len(storage.Anchors))
+	copy(copyOfAnchors, storage.Anchors)
+	storage.Anchors = []types.Anchor{}
+
 	for _, n := range list {
 
 		title := util.GetAttribute(n.Attr, "title")
@@ -175,6 +192,21 @@ func prepareAnchors(node *html.Node, storage *types.PageData) {
 		})
 
 	}
+
+	for _, anchor := range copyOfAnchors {
+		foundIndex := -1
+		for index, i := range storage.Images {
+			if i.Id == anchor.Id {
+				foundIndex = index
+				break
+			}
+		}
+
+		if foundIndex > -1 {
+			storage.Anchors[foundIndex] = anchor
+		}
+	}
+
 }
 
 func prepareImages(node *html.Node, storage *types.PageData) {
@@ -189,6 +221,10 @@ func prepareImages(node *html.Node, storage *types.PageData) {
 		return
 	}
 
+	copyOfImages := make([]types.Image, len(storage.Images))
+	copy(copyOfImages, storage.Images)
+	storage.Images = []types.Image{}
+
 	for _, n := range list {
 
 		alt := util.GetAttribute(n.Attr, "alt")
@@ -215,6 +251,20 @@ func prepareImages(node *html.Node, storage *types.PageData) {
 
 	}
 
+	for _, image := range copyOfImages {
+		foundIndex := -1
+		for index, i := range storage.Images {
+			if i.Id == image.Id {
+				foundIndex = index
+				break
+			}
+		}
+
+		if foundIndex > -1 {
+			storage.Images[foundIndex] = image
+		}
+	}
+
 }
 
 func prepareTranslationJson(node *html.Node, storage *types.PageData) {
@@ -228,6 +278,10 @@ func prepareTranslationJson(node *html.Node, storage *types.PageData) {
 		return
 	}
 
+	copyOfTranslations := make([]types.Translations, len(storage.Translations))
+	copy(copyOfTranslations, storage.Translations)
+	storage.Translations = []types.Translations{}
+
 	for _, n := range list {
 
 		id := util.GetAttribute(n.Attr, "id")
@@ -258,6 +312,21 @@ func prepareTranslationJson(node *html.Node, storage *types.PageData) {
 		})
 
 	}
+
+	for _, translation := range copyOfTranslations {
+		foundIndex := -1
+		for index, t := range storage.Translations {
+			if t.Id == translation.Id {
+				foundIndex = index
+				break
+			}
+		}
+
+		if foundIndex > -1 {
+			storage.Translations[foundIndex] = translation
+		}
+	}
+
 }
 
 func prepareTextNodes(node *html.Node, storage *types.PageData) {
@@ -272,8 +341,26 @@ func prepareTextNodes(node *html.Node, storage *types.PageData) {
 		return
 	}
 
+	copyOfTextNodes := make([]types.Text, len(storage.Text))
+	copy(copyOfTextNodes, storage.Text)
+	storage.Text = []types.Text{}
+
 	runNodes(body, storage)
 
+	for _, text := range copyOfTextNodes {
+		foundIndex := -1
+		for index, t := range storage.Text {
+			if t.Id == text.Id {
+				foundIndex = index
+				break
+			}
+		}
+
+		if foundIndex > -1 {
+			storage.Text[foundIndex] = text
+		}
+	}
+
 }
 
 func runNodes(n *html.Node, storage *types.PageData) {
@@ -340,21 +427,27 @@ func checkNodes(n *html.Node, storage *types.PageData) {
 
 }
 
-func PrepareHtmlFile(from, to string, storage types.PageDataStorage) error {
+func PrepareHtmlFile(from, to string, storage types.PageDataStorage) (string, error) {
 	node, err := util.LoadHtml(from)
 	if err != nil {
-		return err
+		return "", err
 	}
 
+	var pd *types.PageData
+	var ok bool
+
 	p := path.Base(from)
-	pd := types.NewPageData()
-	storage[p] = pd
+
+	if pd, ok = storage[p]; !ok {
+		pd = types.NewPageData()
+		storage[p] = pd
+	}
 
 	prepareLanguage(node, pd)
 	prepareTitle(node, pd)
-	prepateMeta(node, "name", "description", pd)
-	prepateMeta(node, "name", "keywords", pd)
-	prepateMeta(node, "name", "author", pd)
+	prepareMeta(node, "name", "description", pd)
+	prepareMeta(node, "name", "keywords", pd)
+	prepareMeta(node, "name", "author", pd)
 
 	prepareImages(node, pd)
 	prepareAnchors(node, pd)
@@ -365,6 +458,6 @@ func PrepareHtmlFile(from, to string, storage types.PageDataStorage) error {
 	pd.Export = path.Join(pd.Lang, path.Base(from))
 	to = path.Join(to, path.Base(from))
 
-	return util.SaveHtml(to, node)
+	return p, util.SaveHtml(to, node)
 
 }
diff --git a/application/source/util/html.go b/application/source/util/html.go
index 1c26471..26753f2 100644
--- a/application/source/util/html.go
+++ b/application/source/util/html.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"golang.org/x/net/html"
 	"os"
+	"path"
 	"strconv"
 )
 
@@ -76,11 +77,24 @@ func SaveHtml(p string, node *html.Node) error {
 
 	_, err = os.Stat(p)
 	if errors.Is(err, os.ErrNotExist) {
+		var dir = path.Dir(p)
+		err := os.MkdirAll(dir, os.ModePerm)
+		if err != nil {
+			return err
+		}
+
 		f, err = os.Create(p)
+		if err != nil {
+			return err
+		}
+
 	} else if err != nil {
 		return err
 	} else {
-		os.Remove(p)
+		err := os.Remove(p)
+		if err != nil {
+			return err
+		}
 		f, err = os.Create(p)
 		if err != nil {
 			return err
diff --git a/devenv.lock b/devenv.lock
index 31f42d2..fd80158 100644
--- a/devenv.lock
+++ b/devenv.lock
@@ -3,11 +3,11 @@
     "devenv": {
       "locked": {
         "dir": "src/modules",
-        "lastModified": 1688925746,
-        "narHash": "sha256-Lora6gsuAdVeVeGj2nD4joxTu+jZsAwySlxY1XZVq8o=",
+        "lastModified": 1689667485,
+        "narHash": "sha256-tLNoMRSPLlW1D4wgNpSIPUUHd6x1GdjAhETLpRTKnfo=",
         "owner": "cachix",
         "repo": "devenv",
-        "rev": "b058455b37d2a1833eb71bebd3d2b2aa58ce70d7",
+        "rev": "6f8add968bc12bf81d845eb7dc684a0733bb1518",
         "type": "github"
       },
       "original": {
@@ -74,11 +74,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1688925019,
-        "narHash": "sha256-281HjmJycKt8rZ0/vpYTtJuZrQl6mpGNlUFf8cebmeA=",
+        "lastModified": 1689631193,
+        "narHash": "sha256-AGSkBZaiTODQc8eT1rZDrQIjtb8JtFwJ0wVPzArlrnM=",
         "owner": "NixOS",
         "repo": "nixpkgs",
-        "rev": "2b356dae6208d422236c4cdc48f3bed749f9daea",
+        "rev": "57695599bdc4f7bfe5d28cfa23f14b3d8bdf8a5f",
         "type": "github"
       },
       "original": {
@@ -115,11 +115,11 @@
         "nixpkgs-stable": "nixpkgs-stable"
       },
       "locked": {
-        "lastModified": 1688596063,
-        "narHash": "sha256-9t7RxBiKWHygsqXtiNATTJt4lim/oSYZV3RG8OjDDng=",
+        "lastModified": 1689668210,
+        "narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=",
         "owner": "cachix",
         "repo": "pre-commit-hooks.nix",
-        "rev": "c8d18ba345730019c3faf412c96a045ade171895",
+        "rev": "eb433bff05b285258be76513add6f6c57b441775",
         "type": "github"
       },
       "original": {
-- 
GitLab