github.com/pbberlin/go-pwa@v0.0.0-20220328105622-7c26e0ca1ab8/pkg/static/template-executors.go (about)

     1  package static
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"html/template"
     7  	htmltpl "html/template"
     8  	"io"
     9  	"log"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"strings"
    15  	texttpl "text/template"
    16  
    17  	"github.com/pbberlin/go-pwa/pkg/cfg"
    18  )
    19  
    20  // listForPreCaching compiles the list of files with a version
    21  // to be pre-cached by service worker
    22  func (dirs dirsT) listForPreCaching(w http.ResponseWriter) []string {
    23  
    24  	var seed = []string{
    25  		"/index.html",
    26  		"/offline.html",
    27  		// ... dynamic css, js, webp, json
    28  
    29  		// example for external url res
    30  		// "https://fonts.google.com/icon?family=Material+Icons",
    31  	}
    32  
    33  	res := make([]string, len(seed), 16)
    34  	copy(res, seed)
    35  
    36  	for _, dir := range dirs {
    37  
    38  		if dir.isSingleFile {
    39  			if !dir.swpc.cache {
    40  				continue
    41  			}
    42  			// 	these files are requested without
    43  			//  version prefix in the path,
    44  			//  thus we dont need the version prefix here
    45  			res = append(res, dir.urlPath)
    46  			continue
    47  		}
    48  
    49  		files, err := filesOfDir(dir.src)
    50  		if err != nil {
    51  			fmt.Fprint(w, err)
    52  			return res
    53  		}
    54  
    55  		for _, file := range files {
    56  
    57  			if file.IsDir() {
    58  				continue
    59  			}
    60  
    61  			if len(dir.swpc.includeExtensions) > 0 {
    62  				for _, ext := range dir.swpc.includeExtensions {
    63  					if strings.HasSuffix(file.Name(), ext) {
    64  						continue
    65  					}
    66  				}
    67  			}
    68  
    69  			// fmt.Fprintf(w, "\t %v\n", file.Name())
    70  			pth := path.Join(dir.src, cfg.Get().TS, file.Name())
    71  			pth = strings.Replace(pth, "app-bucket/", "./", 1)
    72  			res = append(res, pth)
    73  		}
    74  	}
    75  
    76  	return res
    77  }
    78  
    79  /*
    80  	This is unused and leads nowhere.
    81  
    82  	There is no idiomatic way to fill a template of Javascript
    83  	with snippets of Javascript :-(
    84  
    85  	A clean way woudl be to create a template file
    86  	from a JavaScript file fill its
    87  	JavaScript fields  {{ .MyJavaScriptSnippet  }}
    88  
    89  	We would want the template be of type html.JS or html.JSStr
    90  
    91  	If we use HTML types, we get escapings
    92  
    93  		"<" being escaped to &lt; in the template
    94  
    95  		"'" in .MyJavaScriptSnippet being escaped to &23232;
    96  
    97  
    98  */
    99  func javascriptTemplate(srcPth string, w io.Writer) {
   100  
   101  	_ = htmltpl.HTMLEscapeString("force-import")
   102  	_ = texttpl.HTMLEscapeString("force-import") // avoiding escaping hassle by using text templates => fail
   103  
   104  	btsSW, err := os.ReadFile(srcPth)
   105  	if err != nil {
   106  		fmt.Fprintf(w, "could not read service worker template %v\n", err)
   107  		return
   108  	}
   109  	strSW := string(btsSW)         // file content bytes to string
   110  	strSWAsJS := htmltpl.JS(strSW) // string into type Javascript code
   111  	_ = strSWAsJS
   112  
   113  	//
   114  
   115  	tpl := htmltpl.New("JS body")
   116  	tpl.Parse("let x=1; {{js .JS}}") // we want to put in strSWAsJS, but the argument must be string
   117  
   118  }
   119  
   120  // execServiceWorker executes the service worker template
   121  // and writes the result into the file system;
   122  // see javascriptTemplate for a discussion of alternatives
   123  func execServiceWorker(dirs dirsT, dir dirT, w http.ResponseWriter) {
   124  
   125  	t, err := htmltpl.ParseFiles(dir.srcTpl)
   126  	if err != nil {
   127  		fmt.Fprintf(w, "could not parse template %v: %v\n", dir.srcTpl, err)
   128  		return
   129  	}
   130  
   131  	data := struct {
   132  		Version     string
   133  		ListOfFiles htmltpl.HTML // neither .JS nor .JSStr do work
   134  	}{
   135  		Version:     cfg.Get().TS,
   136  		ListOfFiles: htmltpl.HTML("'" + strings.Join(dirs.listForPreCaching(w), "',\n  '") + "'"),
   137  	}
   138  
   139  	bts := &bytes.Buffer{}
   140  	err = t.Execute(bts, data)
   141  	if err != nil {
   142  		fmt.Fprintf(w, "could not execute template %v: %v\n", dir.srcTpl, err)
   143  		return
   144  	}
   145  
   146  	dstPth := path.Join(dir.src, dir.fn)
   147  	os.WriteFile(dstPth, bts.Bytes(), 0644)
   148  }
   149  
   150  func manifestIconList(w http.ResponseWriter) string {
   151  
   152  	/* template string for each file
   153  		{
   154  			"src": "/img/icon-072.webp",
   155  			"sizes": "72x72",
   156  			"type": "image/png",
   157  			"purpose": "maskable"
   158  	  	},
   159  	*/
   160  	ts := `  {
   161      "src": "/img/{{.FN}}",
   162      "sizes": "{{.Size}}",
   163      "type": "image/png",
   164      "purpose": "{{.MaskableOrAny}}"
   165    }`
   166  
   167  	t := template.New("list-entry")
   168  	t, err := t.Parse(ts)
   169  	if err != nil {
   170  		log.Fatalf("error parsing icon template: %v", err)
   171  	}
   172  
   173  	// globbing for .../img/icon-072.webp, ... /img/icon-128.webp ...
   174  	icons, err := filepath.Glob("./app-bucket/img/icon-???.webp")
   175  	if err != nil {
   176  		log.Fatalf("error reading icons: %v", err)
   177  	}
   178  
   179  	sb := &strings.Builder{}
   180  
   181  	for idx, icon := range icons {
   182  		icon = filepath.Base(icon)
   183  		// log.Printf("\ticon %v", icon)
   184  
   185  		fnp := icon                                       // file name part containing the icon size, i.e. 072
   186  		fnp = strings.TrimSuffix(fnp, filepath.Ext(icon)) // remove trailing .webp
   187  		fnp = strings.TrimPrefix(fnp, "icon-")            // leading  icon-
   188  		if strings.HasPrefix(fnp, "0") {
   189  			fnp = strings.TrimPrefix(fnp, "0")
   190  		}
   191  		// size := "072x072"
   192  		size := fmt.Sprintf("%vx%v", fnp, fnp)
   193  
   194  		maskableOrAny := "maskable"
   195  		if len(fnp) > 2 && fnp > "128" {
   196  			maskableOrAny = "any"
   197  		}
   198  
   199  		data := struct {
   200  			FN            string
   201  			Size          string
   202  			MaskableOrAny string
   203  		}{
   204  			FN:            icon,
   205  			Size:          size,
   206  			MaskableOrAny: maskableOrAny,
   207  		}
   208  		t.Execute(sb, data)
   209  		if idx < len(icons)-1 {
   210  			fmt.Fprint(sb, ",\n") // trailing comma - except for last iteration
   211  		}
   212  	}
   213  
   214  	return sb.String()
   215  }
   216  
   217  func execManifest(dirs dirsT, dir dirT, w http.ResponseWriter) {
   218  
   219  	t, err := htmltpl.ParseFiles(dir.srcTpl)
   220  	if err != nil {
   221  		fmt.Fprintf(w, "could not parse template %v: %v\n", dir.srcTpl, err)
   222  		return
   223  	}
   224  
   225  	data := struct {
   226  		Title       string
   227  		TitleShort  string
   228  		Description string
   229  		IconList    htmltpl.HTML
   230  	}{
   231  		Title:       cfg.Get().Title,
   232  		TitleShort:  cfg.Get().TitleShort,
   233  		Description: cfg.Get().Description,
   234  		IconList:    htmltpl.HTML(manifestIconList(w)),
   235  	}
   236  
   237  	bts := &bytes.Buffer{}
   238  	err = t.Execute(bts, data)
   239  	if err != nil {
   240  		fmt.Fprintf(w, "could not execute template %v: %v\n", dir.srcTpl, err)
   241  		return
   242  	}
   243  
   244  	dstPth := path.Join(dir.src, dir.fn)
   245  	os.WriteFile(dstPth, bts.Bytes(), 0644)
   246  }
   247  
   248  func execDB(dirs dirsT, dir dirT, w http.ResponseWriter) {
   249  
   250  	t, err := htmltpl.ParseFiles(dir.srcTpl)
   251  	if err != nil {
   252  		fmt.Fprintf(w, "could not parse template %v: %v\n", dir.srcTpl, err)
   253  		return
   254  	}
   255  
   256  	data := struct {
   257  		SchemaVersion int
   258  	}{
   259  		SchemaVersion: cfg.Get().SchemaVersion,
   260  	}
   261  
   262  	bts := &bytes.Buffer{}
   263  	err = t.Execute(bts, data)
   264  	if err != nil {
   265  		fmt.Fprintf(w, "could not execute template %v: %v\n", dir.srcTpl, err)
   266  		return
   267  	}
   268  
   269  	dstPth := path.Join(dir.src, dir.fn)
   270  	os.WriteFile(dstPth, bts.Bytes(), 0644)
   271  }