1// ===== cli.go =====
2// Package main cli.go
3package main
4
5import (
6 _ "embed"
7 "fmt"
8 "log"
9 "os"
10 "reflect"
11 "runtime"
12 "strconv"
13 "strings"
14 "time"
15
16 "github.com/0magnet/calvin"
17 "github.com/bitfield/script"
18 cc "github.com/ivanpirog/coloredcobra"
19 "github.com/spf13/cobra"
20 "github.com/stripe/stripe-go/v81"
21 "golang.org/x/text/cases"
22 "golang.org/x/text/language"
23)
24
25func init() {
26 stripe.EnableTelemetry = false
27 rootCmd.CompletionOptions.DisableDefaultCmd = true
28 rootCmd.AddCommand(
29 runCmd,
30 genCmd,
31 )
32 var helpflag bool
33 rootCmd.SetUsageTemplate(help)
34 rootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for "+rootCmd.Use)
35 rootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
36 rootCmd.PersistentFlags().MarkHidden("help") //nolint
37
38}
39
40var rootCmd = &cobra.Command{
41 Use: "m2",
42 Short: "web store server",
43 Long: calvin.AsciiFont("magnetosphere.net") + "\n" + "web store server",
44}
45
46var genCmd = &cobra.Command{
47 Use: "gen",
48 Short: "generate conf template",
49 Long: "generate conf template",
50 Run: func(_ *cobra.Command, _ []string) {
51 fmt.Println(envfiletemplate)
52 },
53}
54
55// Execute executes the root cli command
56func Execute() {
57 cc.Init(&cc.Config{
58 RootCmd: rootCmd,
59 Headings: cc.HiBlue + cc.Bold,
60 Commands: cc.HiBlue + cc.Bold,
61 CmdShortDescr: cc.HiBlue,
62 Example: cc.HiBlue + cc.Italic,
63 ExecName: cc.HiBlue + cc.Bold,
64 Flags: cc.HiBlue + cc.Bold,
65 FlagsDescr: cc.HiBlue,
66 NoExtraNewlines: true,
67 NoBottomNewline: true,
68 })
69 if err := rootCmd.Execute(); err != nil {
70 log.Fatal("Failed to execute command: ", err)
71 }
72}
73
74var menvfile = os.Getenv("MENV")
75
76type flagVars struct {
77 Teststripekey bool
78 ProductsCSV string
79 WebPort int
80 CoreRunWebPort int
81 StripelivePK string
82 StripeliveSK string
83 StripetestPK string
84 StripetestSK string
85 StripeSK string
86 StripePK string
87 Siteimagesrc string
88 Siteordersurl string
89 Sitename string
90 Siteext string
91 Sitedomain string
92 Sitelongname string
93 Sitetagline string
94 Sitemeta string
95 Siteprettyname string
96 Siteprettynamecap string
97 Siteprettynamecaps string
98 SiteASCIILogo string
99 Tgcontact string
100 Tgchannel string
101 UseTinygo bool
102 WasmSRC []string
103 WasmExecPath string
104 WasmExecPathGo string
105 WasmExecPathTinyGo string
106 Gobuild string
107 Tinygobuild string
108 Buildwasmwith string
109 LDFlagsX string
110}
111
112var f = flagVars{
113 // WasmSRC: []string{"wasm/stl2.go","wasm/checkout_wasm.go"},
114 ProductsCSV: "products.csv",
115 WasmExecPath: runtime.GOROOT() + "/misc/wasm/wasm_exec.js", //nolint
116 WasmExecPathGo: runtime.GOROOT() + "/misc/wasm/wasm_exec.js", //nolint
117 WasmExecPathTinyGo: strings.TrimSuffix(runtime.GOROOT(), "go") + "tinygo" + "/targets/wasm_exec.js", //nolint
118 Gobuild: "go build",
119 Tinygobuild: "tinygo build -target=wasm --no-debug",
120 Buildwasmwith: "go build",
121 LDFlagsX: "stripePK",
122}
123
124var (
125 // Hardcoded array of valid shorthand characters, excluding "h"
126 shorthandChars = []rune("abcdefgijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
127 nextShortIndex = 0 // Index for the next shorthand flag
128)
129
130// Get the next available shorthand flag
131func getNextShortFlag() string {
132 if nextShortIndex >= len(shorthandChars) {
133 return ""
134 }
135 short := shorthandChars[nextShortIndex]
136 nextShortIndex++
137 return string(short)
138}
139
140var a = true
141var b = false
142
143func addStringFlag(cmd *cobra.Command, f interface{}, fieldPtr *string, description string) {
144 cmd.Flags().StringVarP(fieldPtr, ccc(fieldPtr, f, b), getNextShortFlag(), scriptExecString(fmt.Sprintf("${%s%s}", ccc(fieldPtr, f, a), func(s string) string {
145 if s != "" {
146 s = "-" + s
147 }
148 return s
149 }(*fieldPtr))), fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)))
150}
151
152func addStringSliceFlag(cmd *cobra.Command, f interface{}, fieldPtr *[]string, description string) {
153 cmd.Flags().StringSliceVarP(
154 fieldPtr,
155 ccc(fieldPtr, f, b),
156 getNextShortFlag(),
157 scriptExecStringSlice(fmt.Sprintf("${%s[@]}", ccc(fieldPtr, f, a))),
158 fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)),
159 )
160}
161
162func addBoolFlag(cmd *cobra.Command, f interface{}, fieldPtr *bool, description string) {
163 cmd.Flags().BoolVarP(fieldPtr, ccc(fieldPtr, f, b), getNextShortFlag(), scriptExecBool(fmt.Sprintf("${%s%s}", ccc(fieldPtr, f, a), func(b bool) string {
164 return "-" + strconv.FormatBool(b)
165 }(*fieldPtr))), fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)))
166}
167func addIntFlag(cmd *cobra.Command, f interface{}, fieldPtr *int, description string) {
168 cmd.Flags().IntVarP(fieldPtr, ccc(fieldPtr, f, b), getNextShortFlag(), scriptExecInt(fmt.Sprintf("${%s%s}", ccc(fieldPtr, f, a), func(i int) string {
169 return fmt.Sprintf("-%d", i)
170 }(*fieldPtr))), fmt.Sprintf("%s env: %s\033[0m\n\r", description, ccc(fieldPtr, f, a)))
171}
172
173func init() {
174 runCmd.Flags().SortFlags = false
175 addStringFlag(runCmd, &f, &f.ProductsCSV, "products csv file")
176 addBoolFlag(runCmd, &f, &f.Teststripekey, "use stripe test api keys instead of live key")
177 addStringFlag(runCmd, &f, &f.StripeliveSK, "stripe live api sk")
178 addStringFlag(runCmd, &f, &f.StripelivePK, "stripe live api pk")
179 addStringFlag(runCmd, &f, &f.StripetestSK, "stripe test api sk")
180 addStringFlag(runCmd, &f, &f.StripetestPK, "stripe test api pk")
181 addIntFlag(runCmd, &f, &f.WebPort, "port to serve on")
182 addStringFlag(runCmd, &f, &f.Siteimagesrc, "domain for images - leave blank to serve images")
183 addStringFlag(runCmd, &f, &f.Siteordersurl, "domain for orders - leave blank for same domain")
184 addStringFlag(runCmd, &f, &f.Sitename, "site name")
185 addStringFlag(runCmd, &f, &f.Siteext, "site domain extension")
186 addStringFlag(runCmd, &f, &f.Sitelongname, "site long name")
187 addStringFlag(runCmd, &f, &f.Sitetagline, "site tag line")
188 addStringFlag(runCmd, &f, &f.Sitemeta, "site meta")
189 addStringFlag(runCmd, &f, &f.Tgcontact, "telegram contact")
190 addStringFlag(runCmd, &f, &f.Tgchannel, "telegram channel")
191 addBoolFlag(runCmd, &f, &f.UseTinygo, "use tinygo instead of go to compile wasm")
192 addStringSliceFlag(runCmd, &f, &f.WasmSRC, "wasm source code files RELATIVE PATHS without '..'")
193}
194
195// change case
196func ccc(val interface{}, strct interface{}, upper bool) string {
197 v := reflect.ValueOf(strct)
198 if v.Kind() == reflect.Ptr {
199 v = v.Elem()
200 }
201 if v.Kind() != reflect.Struct {
202 panic("uc: second argument must be a pointer to a struct")
203 }
204 for i := 0; i < v.NumField(); i++ {
205 field := v.Field(i)
206 if field.CanAddr() && field.Addr().Interface() == val {
207 if upper {
208 return strings.ToUpper(v.Type().Field(i).Name)
209 }
210 return strings.ToLower(v.Type().Field(i).Name)
211 }
212 }
213 return ""
214}
215
216var runCmd = &cobra.Command{
217 Use: "run",
218 Short: "run the web application",
219 Long: calvin.AsciiFont("magnetosphere.net") + "\n" + func() string {
220 helptext := `Run the web application
221Generate a config file first
222
223Config defaults file may also be specified with:
224MENV=m2.conf m2 run
225OR
226MENV=/path/to/m2.conf m2 run
227print the MENV file template with:
228m2 gen`
229 if menvfile == "" {
230 return helptext
231 }
232 if _, err := os.Stat(menvfile); err == nil {
233 return `Run the web application
234
235menv file detected: ` + menvfile
236 }
237 return helptext
238 }(),
239 Run: func(_ *cobra.Command, _ []string) {
240 f.Sitedomain = f.Sitename + f.Siteext
241 log.Println(" Initializing " + f.Sitedomain)
242 fmt.Println(calvin.BlackboardBold(f.Sitedomain))
243 fmt.Println(calvin.AsciiFont(f.Sitedomain))
244
245 f.StripeSK = f.StripeliveSK
246 f.StripePK = f.StripelivePK
247 if f.Teststripekey {
248 f.StripeSK = f.StripetestSK
249 f.StripePK = f.StripetestPK
250 }
251 stripe.Key = f.StripeSK
252
253 // awkward way to do this
254 f.LDFlagsX += "=" + f.StripePK
255
256 f.Siteprettyname = calvin.BlackboardBold(f.Sitedomain) //"ππππππ₯π π€π‘πππ£π.πππ₯"
257 c := cases.Title(language.English)
258 f.Siteprettynamecap = calvin.BlackboardBold(c.String(f.Sitedomain)) //"ππππππ₯π π€π‘πππ£π.πππ₯"
259 f.Siteprettynamecaps = calvin.BlackboardBold(strings.ToUpper(f.Sitedomain)) //"ππΈπΎβπΌπππββπΌβπΌ.βπΌπ"
260 f.SiteASCIILogo = strings.Replace(strings.Replace(calvin.AsciiFont(f.Sitedomain), " ", " ", -1), "\n", "<br>\n", -1)
261
262 if f.UseTinygo {
263 f.WasmExecPath = f.WasmExecPathTinyGo
264 f.Buildwasmwith = f.Tinygobuild
265 }
266 if len(f.WasmSRC) == 0 {
267 f.WasmExecPath = ""
268 f.Buildwasmwith = ""
269 }
270 log.Println("Checking for products CSV")
271 fileInfo, err := os.Stat("products.csv")
272 if err != nil {
273 log.Fatal("Error getting file info:", err)
274 return
275 }
276 lastModTime = fileInfo.ModTime()
277 log.Println("Reading products CSV")
278 allproducts = readCSV("products.csv")
279 go func() {
280 for {
281 fileInfo, err := os.Stat("products.csv")
282 if err != nil {
283 log.Println("Error getting file info:", err)
284 }
285
286 currentModTime := fileInfo.ModTime()
287 if currentModTime != lastModTime {
288 log.Println("CSV file has been modified!")
289 allproducts = readCSV("products.csv")
290 lastModTime = currentModTime
291 }
292
293 time.Sleep(10 * time.Second)
294 }
295 }()
296
297 server()
298 },
299}
300
301var lastModTime time.Time
302
303func scriptExecString(s string) string {
304 z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s"'`, menvfile, s)).String()
305 if err == nil {
306 return strings.TrimSpace(z)
307 }
308 return ""
309}
310
311func scriptExecStringSlice(s string) []string {
312 z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s" "%s"'`, menvfile, "%s\n", s)).Slice()
313 if err == nil {
314 return z
315 }
316 return []string{""}
317}
318
319func scriptExecBool(s string) bool {
320 z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s"'`, menvfile, s)).String()
321 if err == nil {
322 b, err := strconv.ParseBool(z)
323 if err == nil {
324 return b
325 }
326 }
327 return false
328}
329
330/*
331func scriptExecArray(s string) string {
332 y, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; for _i in %s ; do echo "$_i" ; done'`, menvfile, s)).Slice()
333 if err == nil {
334 return strings.Join(y, ",")
335 }
336 return ""
337}
338*/
339
340func scriptExecInt(s string) int {
341 z, err := script.Exec(fmt.Sprintf(`bash -c 'MENV=%s ; if [[ $MENV != "" ]] && [[ -f $MENV ]] ; then source $MENV ; fi ; printf "%s"'`, menvfile, s)).String()
342 if err == nil {
343 if z == "" {
344 return 0
345 }
346 i, err := strconv.Atoi(z)
347 if err == nil {
348 return i
349 }
350 }
351 return 0
352}
353
354const help = "\r\n" +
355 " {{if .HasAvailableSubCommands}}{{end}} {{if gt (len .Aliases) 0}}\r\n\r\n" +
356 "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}\r\n\r\n" +
357 "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " +
358 "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" +
359 "Flags:\r\n" +
360 "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" +
361 "Global Flags:\r\n" +
362 "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n"
363
364const envfiletemplate = `#
365# /etc/m2.conf
366#
367#########################################################################
368# M2 CONFIG TEMPLATE
369# change config defaults
370# or comment values with # to exclude
371#########################################################################
372
373### Stripe Configuration ################################################
374
375#-- Live and test API keys - REQUIRED
376STRIPELIVEPK='pk_live_...'
377STRIPELIVESK='sk_live_...'
378STRIPETESTPK='pk_test_...'
379STRIPETESTSK='sk_test_...'
380
381#-- Use Test Keys
382USETESTKEY=true
383
384### Site Product Data Configuration #####################################
385
386#-- Products CSV path (ex. 'products.csv')
387PRODUCTSCVS=''
388
389#-- Image subdomain (ex. 'https://img.magnetosphere.net')
390# no trailing slash '/' !
391IMGSRC=''
392
393#-- Orders subdomain (ex. 'https://pay.magnetosphere.net')
394# no trailing slash '/' !
395ORDERSURL=''
396
397### Site Configuration ##################################################
398
399#-- Website (Host) Name (domain minus extension - ex. 'magnetosphere')
400SITENAME=''
401
402#-- Website Domain Extension (ex. '.com' '.net')
403SITEEXT=''
404
405#-- Site Long Name (ex. 'magnetosphere electronic surplus')
406SITELONGNAME=''
407
408#-- Site Tag Line (ex. 'we have the technology')
409SITETAGLINE=''
410
411#-- Site Meta Description (ex. 'we have the technology (ββΏβ) electronic surplus for sale')
412SITEMETA=''
413
414#-- Site Telegram Contact
415# DO NOT INCLUDE 'https://t.me/'
416# ex. 'magnetosphere' will display on-site as 'https://t.me/magnetosphere'
417TGCONTACT=''
418
419#-- Site Telegram Channel
420# DO NOT INCLUDE 'https://t.me/'
421# ex. 'magnetospheredotnet' will display on-site as 'https://t.me/magnetospheredotnet'
422TGCHANNEL=''
423
424### Web Server Configuration ############################################
425
426#-- Port to serve http on (ex. '9883')
427WEBPORT='9883'
428`
429
430
431// ===== core.go =====
432// Package main core.go
433package main
434
435import (
436 "bufio"
437 "bytes"
438 "encoding/json"
439 htmpl "html/template"
440 "log"
441 "os"
442 "path/filepath"
443 "strings"
444 ttmpl "text/template"
445 "time"
446
447 "github.com/bitfield/script"
448 "github.com/briandowns/spinner"
449 "github.com/gofiber/fiber/v3"
450)
451
452func watchCORE() {
453 createCORE()
454 buildCORE()
455
456 files := map[string]struct {
457 lastMod time.Time
458 handler func()
459 }{
460 "ui/π.go": {
461 handler: func() {
462 createCORE()
463 buildCORE()
464 },
465 },
466 "htmpl/product.md": {
467 handler: func() {
468 createCORE()
469 buildCORE()
470 },
471 },
472 }
473
474 go func() {
475 for path := range files {
476 fi, err := os.Stat(path)
477 if err != nil {
478 log.Printf("Cannot stat %s: %v", path, err)
479 continue
480 }
481 files[path] = struct {
482 lastMod time.Time
483 handler func()
484 }{
485 lastMod: fi.ModTime(),
486 handler: files[path].handler,
487 }
488 }
489
490 ticker := time.NewTicker(5 * time.Second)
491 defer ticker.Stop()
492
493 for range ticker.C {
494 for path, entry := range files {
495 fi, err := os.Stat(path)
496 if err != nil {
497 log.Printf("Error stating %s: %v", path, err)
498 continue
499 }
500 if fi.ModTime().After(entry.lastMod) {
501 log.Println("π¦ Detected change in", path)
502 files[path] = struct {
503 lastMod time.Time
504 handler func()
505 }{
506 lastMod: fi.ModTime(),
507 handler: entry.handler,
508 }
509 entry.handler()
510 }
511 }
512 }
513 }()
514}
515
516func handleCORE(r *fiber.App) {
517 r.Use("/", func(c fiber.Ctx) error {
518 coreUIPath := "ui/bin/web"
519 trim := strings.Trim(c.Path(), "/")
520 if trim == "" {
521 trim = "index.html"
522 }
523 fullPath := filepath.Join(coreUIPath, trim)
524 info, err := os.Stat(fullPath)
525 if err == nil && info.IsDir() {
526 fullPath = filepath.Join(fullPath, "index.html")
527 }
528 if _, err := os.Stat(fullPath); err != nil {
529 c.Status(fiber.StatusNotFound)
530 return c.SendFile(filepath.Join(coreUIPath, "404.html"))
531 }
532 if strings.HasSuffix(fullPath, ".wasm") {
533 c.Set("Content-Type", "application/wasm")
534 }
535 return c.SendFile(fullPath)
536 })
537}
538
539func buildCORE() {
540 s := spinner.New(spinner.CharSets[14], 25*time.Millisecond)
541 s.Suffix = " Building C.O.R.E UI..."
542 s.Start()
543 _, err := script.Exec(`bash -c 'set -x ; go get -u ./... ; go mod tidy ; go mod vendor ; cd ui || exit 1 ; rm -rf bin ; time go run cogentcore.org/core@main build web -vv -ldflags="-X 'main.`+f.LDFlagsX+`'" || timeout 30 go run .'`).Stdout()
544 s.Stop()
545 if err != nil {
546 log.Println(err)
547 } else {
548 log.Println("β
Done building C.O.R.E UI")
549 }
550}
551
552func createCORE() {
553 log.Println("Creating Content Files")
554 if _, err := script.Exec(`bash -c 'rm -rf ui/content ; cp -r ui/content-bak ui/content'`).Stdout(); err != nil {
555 log.Fatalf(err.Error())
556 }
557 log.Println("Populating Content")
558 prodPageMDTmpl, err := ttmpl.New("index").Funcs(htmpl.FuncMap{
559 "replace": replace, "mul": mul, "div": div,
560 "safeHTML": safeHTML, "safeJS": safeJS, "stripProtocol": stripProtocol,
561 "add": add, "sub": sub, "toFloat": toFloat, "equalsIgnoreCase": equalsIgnoreCase,
562 "getsubcats": getsubcats, "escapesubcat": escapesubcat,
563 "sortsubcats": sortsubcats, "repeat": repeat,
564 }).Parse(h.ProductPageMD())
565 if err != nil {
566 log.Println("Error parsing product page markdown template:", err)
567 log.Fatalf(err.Error())
568 }
569// productMap := make(map[string]string)
570 for _, product := range allproducts {
571 if product.Enable != "TRUE" {
572 continue
573 }
574// productMap[product.Partno] = product.Price
575 var buf bytes.Buffer
576 err := prodPageMDTmpl.Execute(&buf, map[string]interface{}{"Prod": product, "Domain": f.Sitedomain})
577 if err != nil {
578 log.Fatalf(err.Error())
579 }
580 lines := strings.Split(buf.String(), "\n")
581 var cleanedLines []string
582 for _, line := range lines {
583 if strings.TrimSpace(line) != "" {
584 cleanedLines = append(cleanedLines, line)
585 }
586 }
587 cleaned := strings.Join(cleanedLines, "\n") + "\n"
588 filename := "ui/content/" + escapesubcat(product.Partno) + ".md"
589 _, err = script.Echo(cleaned).WriteFile(filename)
590 if err != nil {
591 log.Fatalf(err.Error())
592 }
593 }
594 log.Println("Writing products.json")
595 /*
596 jsonData, err := json.MarshalIndent(productMap, "", " ")
597 if err != nil {
598 log.Println("Error marshaling products.json:", err)
599 os.Exit(1)
600 }
601 _, err = script.Echo(string(jsonData)).WriteFile("ui/products.json")
602 if err != nil {
603 panic(err)
604 os.Exit(1)
605 }
606 */
607
608 data := readproductscsv(f.ProductsCSV)
609 if len(data) == 0 {
610 log.Fatalf("CSV file %s is empty or unreadable", f.ProductsCSV)
611 }
612 scanner := bufio.NewScanner(bytes.NewReader(data))
613 var jsonData []map[string]string
614 var headers []string
615 lineNum := 0
616 for scanner.Scan() {
617 line := scanner.Text()
618 f := strings.Split(line, ",")
619 if lineNum == 0 {
620 headers = f
621 lineNum++
622 continue
623 }
624 if len(f) < 4 {
625 continue
626 }
627 if f[3] == "TRUE" {
628 entry := make(map[string]string)
629 for i := range f {
630 if i < len(headers) {
631 entry[headers[i]] = f[i]
632 }
633 }
634 jsonData = append(jsonData, entry)
635 }
636 lineNum++
637 }
638
639 if err := scanner.Err(); err != nil {
640 log.Fatalf("Error scanning CSV: %v", err)
641 }
642
643 // Convert to JSON
644 output, err := json.MarshalIndent(jsonData, "", " ")
645 if err != nil {
646 log.Fatalf("Error encoding JSON: %v", err)
647 }
648
649 _, err = script.Echo(string(output)).WriteFile("ui/products.json")
650 if err != nil {
651 log.Fatalf(err.Error())
652 }
653}
654
655
656// ===== csv.go =====
657// Package main csv.go
658package main
659
660import (
661 "bufio"
662 "bytes"
663 _ "embed"
664 "log"
665 "strings"
666
667 p "github.com/0magnet/m2/pkg/product"
668 "github.com/bitfield/script"
669)
670
671var allproducts p.Products
672
673func readproductscsv(csvFile string) (data []byte) {
674 data, err := script.File(csvFile).Bytes() //nolint
675 if err != nil {
676 log.Printf(`Error reading %s file %v`, csvFile, err)
677 }
678 return data
679}
680
681func readCSV(csvFile string) (prods p.Products) {
682 scanner := bufio.NewScanner(bytes.NewReader(readproductscsv(csvFile)))
683 for scanner.Scan() {
684 line := scanner.Text()
685 f := strings.Split(line, ",")
686 if len(f) < 4 {
687 continue
688 }
689 if f[3] == "TRUE" {
690 q := p.Product{
691 Image1: f[0],
692 Partno: f[1],
693 Name: f[2],
694 Enable: f[3],
695 Price: f[4],
696 Quantity: f[5],
697 Shippable: f[6],
698 Minorder: f[7],
699 Maxorder: f[8],
700 Defaultquantity: f[9],
701 Stepquantity: f[10],
702 Mfgpartno: f[11],
703 Mfgname: f[12],
704 Category: f[13],
705 Subcategory: f[14],
706 Location: f[15],
707 Msrp: f[16],
708 Cost: f[17],
709 Typ: f[18],
710 Packagetype: f[19],
711 Technology: f[20],
712 Materials: f[21],
713 Value: f[22],
714 ValUnit: f[23],
715 Resistance: f[24],
716 ResUnit: f[25],
717 Tolerance: f[26],
718 VoltsRating: f[27],
719 AmpsRating: f[28],
720 WattsRating: f[29],
721 TempRating: f[30],
722 TempUnit: f[31],
723 Description1: f[32],
724 Description2: f[33],
725 Color1: f[34],
726 Color2: f[35],
727 Sourceinfo: f[36],
728 Datasheet: f[37],
729 Docs: f[38],
730 Reference: f[39],
731 Attributes: f[40],
732 Year: f[41],
733 Condition: f[42],
734 Note: f[43],
735 Warning: f[44],
736 CableLengthInches: f[45],
737 LengthInches: f[46],
738 WidthInches: f[47],
739 HeightInches: f[48],
740 WeightLb: f[49],
741 WeightOz: f[50],
742 }
743 prods = append(prods, q)
744 }
745 }
746 return prods
747}
748
749
750// ===== m2.go =====
751// Package main m2.go
752package main
753
754import (
755 "bytes"
756 "encoding/base64"
757 "errors"
758 "fmt"
759 "log"
760 "path/filepath"
761 "sort"
762 "strings"
763 "sync"
764 "regexp"
765 "time"
766
767 p "github.com/0magnet/m2/pkg/product"
768 "github.com/bitfield/script"
769 "github.com/gofiber/fiber/v3"
770 "github.com/gofiber/fiber/v3/middleware/static"
771)
772
773func main() { Execute()}
774
775var collapseNewlines = regexp.MustCompile(`\n{2,}`)
776
777func methodColor(method string, colors fiber.Colors) string {
778 switch method {
779 case fiber.MethodGet:
780 return colors.Cyan
781 case fiber.MethodPost:
782 return colors.Green
783 case fiber.MethodPut:
784 return colors.Yellow
785 case fiber.MethodDelete:
786 return colors.Red
787 case fiber.MethodPatch:
788 return colors.White
789 case fiber.MethodHead:
790 return colors.Magenta
791 case fiber.MethodOptions:
792 return colors.Blue
793 default:
794 return colors.Reset
795 }
796}
797
798func statusColor(code int, colors fiber.Colors) string {
799 switch {
800 case code >= fiber.StatusOK && code < fiber.StatusMultipleChoices:
801 return colors.Green
802 case code >= fiber.StatusMultipleChoices && code < fiber.StatusBadRequest:
803 return colors.Blue
804 case code >= fiber.StatusBadRequest && code < fiber.StatusInternalServerError:
805 return colors.Yellow
806 default:
807 return colors.Red
808 }
809}
810
811func server() {
812 wg := new(sync.WaitGroup)
813 wg.Add(1)
814 initTMPL()
815 r := fiber.New(fiber.Config{
816 ErrorHandler: func(c fiber.Ctx, err error) error {
817 code := fiber.StatusInternalServerError
818 var e *fiber.Error
819 if errors.As(err, &e) {
820 code = e.Code
821 }
822 c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8)
823 return c.Status(code).SendString(err.Error())
824 },
825 })
826
827
828 r.Use(func(c fiber.Ctx) error {
829 start := time.Now()
830 err := c.Next()
831 status := c.Response().StatusCode()
832 lat := time.Since(start)
833 colors := c.App().Config().ColorScheme
834 ip := fmt.Sprintf("%*s", 15, c.IP())
835 ipsStr := strings.Join(c.IPs(), ", ")
836 ips := fmt.Sprintf("%*s", 15, ipsStr)
837 method := fmt.Sprintf("%-*s", 6, c.Method())
838 statCol := statusColor(status, colors) + fmt.Sprintf("%3d", status) + colors.Reset
839 methCol := methodColor(c.Method(), colors) + method + colors.Reset
840 fmt.Printf("%s | %s | %12s | %s | %s | %s | %s\n",time.Now().Format("2006-01-02 15:04:05"),statCol,lat,ip,ips,methCol,c.Path())
841 return err
842 })
843 serveSourceCode(r)
844 serveWASM(r)
845 r.Get("/logo.png", sendFile)
846 r.Get("/logo.html", sendFile)
847 r.Get("/mobilelogo.html", sendFile)
848 r.Get("/logolarge.html", sendFile)
849 r.Get("/favicon.ico", sendImage)
850 r.Get("/robots.txt", robots)
851 if f.Siteimagesrc == "" {
852 r.Use("/i", static.New("./img"))
853 r.Use("/img", static.New("./img"))
854 }
855 r.Get("/stl/:filename", func(c fiber.Ctx) error { return c.SendFile("./img/stl/" + c.Params("filename")) })
856 r.Get("/stl/base64/:filename", stlbase64)
857 r.Get("/site.webmanifest", func(c fiber.Ctx) error {return c.JSON([]byte(`{"name":"","short_name":"","icons":[{"src":"` + f.Siteimagesrc + `/i/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"` + f.Siteimagesrc + `/i/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}`))})
858 r.Get("/sitemap", sitemap)
859 r.Get("/sitemap.xml", sitemap)
860 r.Get("/coffee", func(c fiber.Ctx) error {return c.SendStatus(fiber.StatusTeapot)})
861 r.Get("/clock", clock)
862 r.Get("/", homepage)
863 r.Get("/p/:partno", productpage)
864 r.Get("/post/:partno", handlecat)
865 r.Get("/p", handlecat)
866 r.Get("/cat", handlecat)
867 r.Get("/cat/:cat", handlecat)
868 r.Get("/cat/:cat/:subcat", handlecat)
869 r.Get("/style.css", style)
870 handleOthers(r)
871 handleOrder(r)
872 handleCORE(r)
873 go func() {
874 err := r.Listen(fmt.Sprintf(":%d", f.WebPort))
875 if err != nil {
876 log.Println("Error serving http: ", err)
877 }
878 wg.Done()
879 }()
880 watchCORE()
881 compileWASM()
882 wg.Wait()
883}
884
885
886func sitemap(c fiber.Ctx) error {
887 c.Type("xml", "utf-8")
888 return c.SendString(generateSitemapXML())
889}
890
891func clock(c fiber.Ctx) error {
892 c.Set("Content-Type", "text/html;charset=utf-8")
893 _, err := c.Status(fiber.StatusOK).Write([]byte(h.Clock()))
894 return err
895}
896
897func robots(c fiber.Ctx) error {
898 c.Set("Content-Type", "text/html;charset=utf-8")
899 _, err := c.Status(fiber.StatusOK).Write([]byte(fmt.Sprintf("User-Agent: *\n\nSitemap: https://%s/sitemap", c.OriginalURL())))
900 return err
901}
902
903func style(c fiber.Ctx) error {
904 c.Set("Content-Type", "text/css;charset=utf-8")
905 _, err := c.Status(fiber.StatusOK).Write([]byte(h.StyleCSS()))
906 return err
907}
908
909func serveWASM(r *fiber.App) {
910 if f.WasmExecPath != "" {
911 _, err := script.File(f.WasmExecPath).Bytes()
912 if err != nil {
913 log.Printf("Error reading %s: %v\n", f.WasmExecPath, err)
914 } else { //the wasm exec must be present or none of the webassembly stuff will work ; provided by the golang installaton
915 r.Get(f.WasmExecPathTinyGo, func(c fiber.Ctx) error {
916 wasmExecData, err := script.File(f.WasmExecPathTinyGo).Bytes()
917 if err != nil {
918 log.Printf("Error reading %s: %v\n", f.WasmExecPathTinyGo, err)
919 return c.SendStatus(fiber.StatusNotFound)
920 }
921 c.Set("Content-Type", "application/js")
922 _, err = c.Status(fiber.StatusOK).Write(wasmExecData)
923 return err
924 })
925
926 r.Get(f.WasmExecPathGo, func(c fiber.Ctx) error {
927 wasmExecData, err := script.File(f.WasmExecPathGo).Bytes()
928 if err != nil {
929 log.Printf("Error reading %s: %v\n", f.WasmExecPathGo, err)
930 return c.SendStatus(fiber.StatusNotFound)
931 }
932 c.Set("Content-Type", "application/js")
933 _, err = c.Status(fiber.StatusOK).Write(wasmExecData)
934 return err
935 })
936
937 suffix := ".wasm"
938 if f.UseTinygo {
939 suffix = "-tiny.wasm"
940 }
941 for _, wasmSRC := range f.WasmSRC {
942 outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + suffix
943 r.Get("/"+outputFile, func(c fiber.Ctx) error {
944 data, err := script.File(outputFile).Bytes()
945 if err != nil {
946 script.File(outputFile).Stdout() //nolint
947 return c.SendStatus(fiber.StatusInternalServerError)
948 }
949 c.Set("Content-Type", "application/wasm")
950 return c.Status(fiber.StatusOK).Send(data)
951 })
952 }
953 }
954 }
955}
956
957func sendFile(c fiber.Ctx) error {
958 return c.SendFile("." + c.Path())
959}
960func sendImage(c fiber.Ctx) error {
961 c.Set("Content-Type", "image/jpeg")
962 return c.SendFile("./i" + c.Path())
963}
964
965func stlbase64(c fiber.Ctx) error {
966 stlfile, err := script.File("img/stl/" + c.Params("filename")).Bytes()
967 if err != nil {
968 _, _ = script.File("img/stl/" + c.Params("filename")).Stdout() //nolint
969 return c.SendStatus(fiber.StatusNotFound)
970 }
971 _, err = c.Status(fiber.StatusOK).Write([]byte("data:model/stl;base64," + base64.StdEncoding.EncodeToString(stlfile)))
972 return err
973}
974
975type item struct {
976 ID string
977 Amount int64
978}
979
980func cathtmlfunc(c fiber.Ctx) error {
981 tmpl, err := mainTmpl()
982 if err != nil {
983 msg := fmt.Sprintf("Error parsing html template: %v", err)
984 log.Println(msg)
985 return c.Status(fiber.StatusInternalServerError).SendString(msg)
986 }
987 tmpl0, err := tmpl.Clone()
988 if err != nil {
989 msg := fmt.Sprintf("Error cloning html template: %v", err)
990 log.Println(msg)
991 return c.Status(fiber.StatusInternalServerError).SendString(msg)
992 }
993 _, err = tmpl0.New("main").Parse(h.CategoryPage())
994 if err != nil {
995 msg := fmt.Sprintf("Error parsing Category page template: %v", err)
996 log.Println(msg)
997 return c.Status(fiber.StatusInternalServerError).SendString(msg)
998 }
999 tmpl = tmpl0
1000 var tmplData map[string]interface{}
1001 var result bytes.Buffer
1002 var categoryproducts p.Products
1003 c.Set("Content-Type", "text/html;charset=utf-8")
1004 h1 := pageMeta(c, htmlPageTemplateData)
1005 h1.Title = fmt.Sprintf("%s | %s", func() string {
1006 var str string
1007 if c.Params("partno") != "" {
1008 return "No product matching partno.: " + c.Params("partno") + " | Showing All Products"
1009 }
1010 if c.Params("cat") == "" {
1011 return "All Products"
1012 }
1013 str = fmt.Sprintf("Category: %s", c.Params("cat"))
1014 if c.Params("subcat") != "" {
1015 str += fmt.Sprintf("; Subcategory: %s", c.Params("subcat"))
1016 }
1017 return str
1018 }(), h1.Title)
1019 h1.Page = "category"
1020 if c.Params("cat") == "" && c.Params("subcat") == "" {
1021 tmplData = map[string]interface{}{
1022 "Products": allproducts,
1023 "Page": h1,
1024 "Category": c.Params("cat"),
1025 "Subcategory": c.Params("subcat"),
1026 "Prods": allproducts,
1027 "Product": c.Params("partno"),
1028 }
1029 } else {
1030
1031 for _, prod := range allproducts {
1032 if strings.EqualFold(prod.Category, c.Params("cat")) && (c.Params("subcat") == "" || strings.EqualFold(escapesubcat(prod.Subcategory), c.Params("subcat"))) {
1033 categoryproducts = append(categoryproducts, prod)
1034 }
1035 }
1036 tmplData = map[string]interface{}{
1037 "Products": categoryproducts,
1038 "Page": h1,
1039 "Category": c.Params("cat"),
1040 "Subcategory": c.Params("subcat"),
1041 "Prods": allproducts,
1042 }
1043 }
1044 err = tmpl.Execute(&result, tmplData)
1045 if err != nil {
1046 msg := fmt.Sprintf("Error execute html template: %v", err)
1047 log.Println(msg)
1048 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1049 }
1050 _, err = c.Status(fiber.StatusOK).Write(collapseNewlines.ReplaceAll(result.Bytes(), []byte("\n")))
1051 return err
1052}
1053
1054func getcats() (cats []string) {
1055 var catsMap = make(map[string]int)
1056 for _, prod := range allproducts {
1057 catsMap[prod.Category]++
1058 }
1059 for cat := range catsMap {
1060 cats = append(cats, cat)
1061 }
1062 return cats
1063}
1064func contains(slice []string, str string) bool {
1065 for _, s := range slice {
1066 if s == str {
1067 return true
1068 }
1069 }
1070 return false
1071}
1072func getcategories(allproducts p.Products) (map[string]int, []string, map[string]map[string]int, map[string][]string) {
1073 categoryCounts := make(map[string]int)
1074 subcategoryCounts := make(map[string]map[string]int)
1075 subcategoriesByCategory := make(map[string][]string)
1076
1077 for _, prod := range allproducts {
1078 if prod.Category != "" {
1079 categoryCounts[prod.Category]++
1080 if prod.Subcategory != "" {
1081 if subcategoryCounts[prod.Category] == nil {
1082 subcategoryCounts[prod.Category] = make(map[string]int)
1083 }
1084 subcategoryCounts[prod.Category][prod.Subcategory]++
1085 if !contains(subcategoriesByCategory[prod.Category], prod.Subcategory) {
1086 subcategoriesByCategory[prod.Category] = append(subcategoriesByCategory[prod.Category], prod.Subcategory)
1087 }
1088 }
1089 }
1090 }
1091
1092 var sortableCategories []struct {
1093 Name string
1094 Count int
1095 }
1096 for cat, count := range categoryCounts {
1097 sortableCategories = append(sortableCategories, struct {
1098 Name string
1099 Count int
1100 }{Name: cat, Count: count})
1101 }
1102 sort.Slice(sortableCategories, func(i, j int) bool {
1103 return sortableCategories[i].Count > sortableCategories[j].Count
1104 })
1105 var sortedCategories []string
1106 for _, cat := range sortableCategories {
1107 sortedCategories = append(sortedCategories, cat.Name)
1108 var sortableSubcategories []struct {
1109 Name string
1110 Count int
1111 }
1112 for subcat, count := range subcategoryCounts[cat.Name] {
1113 sortableSubcategories = append(sortableSubcategories, struct {
1114 Name string
1115 Count int
1116 }{Name: subcat, Count: count})
1117 }
1118 sort.Slice(sortableSubcategories, func(i, j int) bool {
1119 return sortableSubcategories[i].Count > sortableSubcategories[j].Count
1120 })
1121 var sortedSubcategories []string
1122 for _, subcat := range sortableSubcategories {
1123 sortedSubcategories = append(sortedSubcategories, subcat.Name)
1124 }
1125 subcategoriesByCategory[cat.Name] = sortedSubcategories
1126 }
1127 return categoryCounts, sortedCategories, subcategoryCounts, subcategoriesByCategory
1128}
1129
1130func getsubcats(cat string) (subcats []string) {
1131 var subcatsMap = make(map[string]int)
1132 for _, prod := range allproducts {
1133 if cat == "" || strings.EqualFold(cat, prod.Category) {
1134 if prod.Subcategory != "" {
1135 subcatsMap[escapesubcat(prod.Subcategory)]++
1136 }
1137 }
1138 }
1139 for subcat := range subcatsMap {
1140 subcats = append(subcats, subcat)
1141 }
1142 return subcats
1143}
1144func escapesubcat(sc string) (esc string) {
1145 esc = strings.Replace(sc, "ΒΌ", "quarter-", -1)
1146 esc = strings.Replace(esc, "Β½", "half-", -1)
1147 esc = strings.Replace(esc, "1/16", "sixteenth-", -1)
1148 esc = strings.Replace(esc, "%", "-pct", -1)
1149 esc = strings.Replace(esc, " ", " ", -1)
1150 esc = strings.Replace(esc, " ", "-", -1)
1151 esc = strings.Replace(esc, "--", "-", -1)
1152 esc = strings.Replace(esc, "watt1", "watt-1", -1)
1153 esc = strings.Replace(esc, "watt5", "watt-5", -1)
1154 return esc
1155}
1156
1157func handlecat(c fiber.Ctx) error {
1158 if c.Params("cat") == "" && c.Params("subcat") == "" {
1159 return cathtmlfunc(c)
1160 }
1161 var catexists bool
1162 var subcatexists bool
1163 catexists = false
1164 for _, cat := range getcats() {
1165 if strings.EqualFold(cat, c.Params("cat")) {
1166 catexists = true
1167 break
1168 }
1169 }
1170 subcatexists = false
1171 if c.Params("subcat") != "" {
1172 for _, subcat := range getsubcats("") {
1173 if strings.EqualFold(escapesubcat(subcat), c.Params("subcat")) {
1174 subcatexists = true
1175 break
1176 }
1177 }
1178 }
1179 if c.Params("subcat") != "" && !subcatexists {
1180 log.Printf("subcategory %s does not match any existing subcategory\n", c.Params("subcat"))
1181 return c.Redirect().To("/cat/" + c.Params("cat"))
1182 }
1183 if !catexists {
1184 log.Printf("category %s does not match any existing category\n", c.Params("cat"))
1185 return c.Redirect().To("/cat")
1186 }
1187 if catexists || (catexists && subcatexists) {
1188 return cathtmlfunc(c)
1189 }
1190 return c.SendStatus(fiber.StatusNotFound)
1191}
1192
1193
1194func homepage(c fiber.Ctx) error {
1195 tmpl, err := mainTmpl()
1196 if err != nil {
1197 msg := fmt.Sprintf("Could not parsing html template: %v", err)
1198 log.Println(msg)
1199 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1200 }
1201 tmpl0, err := tmpl.Clone()
1202 if err != nil {
1203 msg := fmt.Sprintf("Error cloning template: %v", err)
1204 log.Println(msg)
1205 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1206 }
1207 _, err = tmpl0.New("main").Parse(h.FrontPage())
1208 if err != nil {
1209 msg := fmt.Sprintf("Error parsing Front Page template: %v", err)
1210 log.Println(msg)
1211 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1212 }
1213 _, err = tmpl0.New("about").Parse(h.AboutPage())
1214 if err != nil {
1215 msg := fmt.Sprintf("Error parsing About Page template: %v", err)
1216 log.Println(msg)
1217 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1218 }
1219 _, err = tmpl0.New("policy").Parse(h.PolicyPage())
1220 if err != nil {
1221 msg := fmt.Sprintf("Error parsing Policy Page template: %v", err)
1222 log.Println(msg)
1223 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1224 }
1225 _, err = tmpl0.New("links").Parse(h.LinksPage())
1226 if err != nil {
1227 msg := fmt.Sprintf("Error parsing Links Page template: %v", err)
1228 log.Println(msg)
1229 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1230 }
1231 tmpl = tmpl0
1232 log.Println(c.Get("User-Agent"))
1233 c.Set("Content-Type", "text/html;charset=utf-8")
1234 h1 := pageMeta(c, htmlPageTemplateData)
1235 tmplData := map[string]interface{}{
1236 "Page": h1,
1237 "Prods": allproducts,
1238 }
1239 var result bytes.Buffer
1240 err = tmpl.Execute(&result, tmplData)
1241 if err != nil {
1242 msg := fmt.Sprintf("Error executing template: %v", err)
1243 log.Println(msg)
1244 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1245 }
1246 _, err = c.Status(fiber.StatusOK).Write(collapseNewlines.ReplaceAll(result.Bytes(), []byte("\n")))
1247 return err
1248}
1249
1250
1251func productpage(c fiber.Ctx) error {
1252 tmpl, err := mainTmpl()
1253 if err != nil {
1254 msg := fmt.Sprintf("Error parsing html template: %v", err)
1255 log.Println(msg)
1256 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1257 }
1258 tmpl0, err := tmpl.Clone()
1259 if err != nil {
1260 msg := fmt.Sprintf("Error cloning template: %v", err)
1261 log.Println(msg)
1262 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1263 }
1264 _, err = tmpl0.New("main").Parse(h.ProductPage())
1265 if err != nil {
1266 msg := fmt.Sprintf("Error parsing product page template: %v", err)
1267 log.Println(msg)
1268 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1269 }
1270 tmpl = tmpl0
1271 c.Set("Content-Type", "text/html;charset=utf-8")
1272 for _, prod := range allproducts {
1273 if strings.EqualFold(prod.Partno, c.Params("partno")) {
1274 var result bytes.Buffer
1275 h1 := pageMeta(c, htmlPageTemplateData)
1276 h1.Page = "product"
1277 h1.Title = fmt.Sprintf("%s | %s", prod.Name, h1.Title)
1278 tmplData := map[string]interface{}{
1279 "Prod": prod,
1280 "Page": h1,
1281 "Prods": allproducts,
1282 }
1283 err := tmpl.Execute(&result, tmplData)
1284 if err != nil {
1285 log.Println("error: ", err)
1286 _, err = c.Status(fiber.StatusInternalServerError).Write(result.Bytes())
1287 return err
1288 }
1289 _, err = c.Status(fiber.StatusOK).Write(collapseNewlines.ReplaceAll(result.Bytes(), []byte("\n")))
1290 return err
1291 }
1292 }
1293 log.Printf("product %s does not match any existing product\n", c.Params("partno"))
1294 return c.Status(fiber.StatusNotFound).Redirect().To("/cat")
1295}
1296
1297
1298// ===== order.go =====
1299// Package main stripe.go
1300package main
1301
1302import (
1303 "bytes"
1304 "encoding/json"
1305 "fmt"
1306 htmpl "html/template"
1307 "log"
1308 "os"
1309 "path/filepath"
1310 "time"
1311
1312 "github.com/bitfield/script"
1313 "github.com/gofiber/fiber/v3"
1314 "github.com/stripe/stripe-go/v81"
1315 "github.com/stripe/stripe-go/v81/paymentintent"
1316)
1317
1318func handleOrder(r *fiber.App) {
1319 r.Get("/checkout.css", func(c fiber.Ctx) error {
1320 c.Set("Content-Type", "text/css;charset=utf-8")
1321 _, err := c.Status(fiber.StatusOK).Write([]byte(h.CheckoutCSS()))
1322 return err
1323 })
1324
1325 r.Get("/complete", func(c fiber.Ctx) error {
1326 // Complete template
1327 completetmpl := htmpl.New("index")
1328 if _, err := completetmpl.Parse(h.CompletePage()); err != nil {
1329 msg := fmt.Sprintf("Error parsing complete page template: %v", err)
1330 log.Println(msg)
1331 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1332 }
1333 if _, err := completetmpl.New("wasm").Parse(h.Wasm()); err != nil {
1334 log.Println("Error parsing wasm template:", err)
1335 msg := fmt.Sprintf("Error parsing wasm template: %v", err)
1336 log.Println(msg)
1337 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1338 }
1339 h1 := htmlPageTemplateData
1340 h1.Canonical = c.Protocol() + `://` + c.Hostname() + c.OriginalURL()
1341 h1.BaseURL = c.Protocol() + `://` + c.Hostname()
1342 h1.RequestHost = c.Hostname()
1343 h1.Protocol = c.Protocol()
1344 h1.Time = time.Now().Format(time.RFC3339Nano)
1345 h1.Year = fmt.Sprintf("%v", time.Now().Year())
1346 tmplData := map[string]interface{}{
1347 "Page": h1,
1348 }
1349 var result bytes.Buffer
1350 err := completetmpl.Execute(&result, tmplData)
1351 if err != nil {
1352 msg := fmt.Sprintf("Could not execute html template %v", err)
1353 log.Println(msg)
1354 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1355 }
1356 c.Set("Content-Type", "text/html;charset=utf-8")
1357 return c.Status(fiber.StatusOK).Send(result.Bytes())
1358 })
1359
1360 r.Get("/order/:piid", func(c fiber.Ctx) error {
1361 piid := c.Params("piid")
1362 order, err := script.File("orders/" + piid + ".json").Bytes()
1363 if err != nil {
1364 return c.Status(fiber.StatusNotFound).SendString("Order not found")
1365 }
1366 return c.Status(fiber.StatusOK).Send(order)
1367 })
1368
1369 r.Post("/create-payment-intent", func(c fiber.Ctx) error {
1370 rawBody := c.Body()
1371 if rawBody == nil {
1372 log.Printf("Failed to read raw request body")
1373 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to read request body"})
1374 }
1375 log.Printf("Raw request body: %s", string(rawBody))
1376
1377 var req struct {
1378 Items []item `json:"items"`
1379 }
1380 if err := json.Unmarshal(rawBody, &req); err != nil {
1381 log.Printf("Failed to parse JSON: %v", err)
1382 return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
1383 }
1384
1385 total := int64(0)
1386 for _, item := range req.Items {
1387 total += item.Amount
1388 }
1389
1390 params := &stripe.PaymentIntentParams{
1391 Amount: stripe.Int64(total),
1392 Currency: stripe.String(string(stripe.CurrencyUSD)),
1393 }
1394 pi, err := paymentintent.New(params)
1395 if err != nil {
1396 log.Printf("Failed to create PaymentIntent: %v", err)
1397 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
1398 }
1399
1400 log.Printf("Created PaymentIntent with ClientSecret: %v", pi.ClientSecret)
1401 return c.Status(fiber.StatusOK).JSON(fiber.Map{
1402 "clientSecret": pi.ClientSecret,
1403 "dpmCheckerLink": fmt.Sprintf("https://dashboard.stripe.com/settings/payment_methods/review?transaction_id=%s", pi.ID),
1404 })
1405 })
1406
1407 r.Post("/submit-order", func(c fiber.Ctx) error {
1408 var requestData struct {
1409 LocalStorageData map[string]interface{} `json:"localStorageData"`
1410 PaymentIntentID string `json:"paymentIntentId"`
1411 }
1412
1413 if err := c.Bind().Body(requestData); err != nil {
1414 return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request data"})
1415 }
1416
1417 log.Printf("Received order data: %+v", requestData.LocalStorageData)
1418 log.Printf("Received payment intent ID: %s", requestData.PaymentIntentID)
1419
1420 paymentIntent, err := paymentintent.Get(requestData.PaymentIntentID, nil)
1421 if err != nil {
1422 log.Printf("Error retrieving payment intent: %v", err)
1423 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to verify payment"})
1424 }
1425
1426 if paymentIntent.Status != stripe.PaymentIntentStatusSucceeded {
1427 log.Printf("Payment was not successful, status: %s", paymentIntent.Status)
1428 return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Payment not successful"})
1429 }
1430
1431 ordersDir := "./orders"
1432 if err := os.MkdirAll(ordersDir, os.ModePerm); err != nil { //nolint
1433 log.Printf("Error creating orders directory: %v", err)
1434 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save order"})
1435 }
1436
1437 filePath := filepath.Join(ordersDir, fmt.Sprintf("%s.json", requestData.PaymentIntentID))
1438
1439 data, err := json.MarshalIndent(requestData.LocalStorageData, "", " ")
1440 if err != nil {
1441 log.Printf("Error marshaling data to json: %v", err)
1442 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save order"})
1443 }
1444
1445 if err := os.WriteFile(filePath, data, 0644); err != nil { //nolint
1446 log.Printf("Error writing data to file: %v", err)
1447 return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save order"})
1448 }
1449
1450 return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Order submitted successfully"})
1451 })
1452}
1453
1454
1455// ===== other.go =====
1456// Package main other.go
1457package main
1458
1459import (
1460 "bytes"
1461 "fmt"
1462 "log"
1463
1464 "github.com/gofiber/fiber/v3"
1465)
1466
1467func handleOthers(r *fiber.App) {
1468 r.Get("/COVID", func(c fiber.Ctx) error {
1469 tmpl, err := mainTmpl()
1470 if err != nil {
1471 msg := fmt.Sprintf("Error parse html template: %v", err)
1472 log.Println(msg)
1473 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1474 }
1475 tmpl0, err := tmpl.Clone()
1476 if err != nil {
1477 msg := fmt.Sprintf("Error cloning template: %v", err)
1478 log.Println(msg)
1479 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1480 }
1481 _, err = tmpl0.New("main").Parse(h.COVIDPage())
1482 if err != nil {
1483 msg := fmt.Sprintf("Error parsing main template: %v", err)
1484 log.Println(msg)
1485 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1486 }
1487 tmpl = tmpl0
1488 log.Println(c.Get("User-Agent"))
1489 c.Set("Content-Type", "text/html;charset=utf-8")
1490 h1 := pageMeta(c, htmlPageTemplateData)
1491 h1.Page = "hidden"
1492 h1.MetaDesc = "The COVID ΜΆvΜΆaΜΆcΜΆcΜΆiΜΆnΜΆeΜΆ bioweapon injection genocide and the new dark age of humanity"
1493 // h1.Mobile = strings.Contains(strings.ToLower(c.Get("User-Agent")), "mobile")
1494 tmplData := map[string]interface{}{
1495 "Page": h1,
1496 "Prods": allproducts,
1497 }
1498 var result bytes.Buffer
1499 err = tmpl.Execute(&result, tmplData)
1500 if err != nil {
1501 msg := fmt.Sprintf("Error executing template: %v", err)
1502 log.Println(msg)
1503 return c.Status(fiber.StatusInternalServerError).SendString(msg)
1504 }
1505 _, err = c.Status(fiber.StatusOK).Write(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(bytes.Replace(result.Bytes(), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1), []byte("\n\n"), []byte("\n"), -1))
1506 return err
1507 })
1508
1509}
1510
1511
1512// ===== source.go =====
1513// Package main source.go
1514package main
1515
1516import (
1517 "bytes"
1518 "embed"
1519 "fmt"
1520 "strings"
1521 "io/fs"
1522 "os"
1523
1524 "github.com/alecthomas/chroma/v2"
1525 "github.com/alecthomas/chroma/v2/formatters/html"
1526 "github.com/alecthomas/chroma/v2/lexers"
1527 "github.com/alecthomas/chroma/v2/styles"
1528 "github.com/gofiber/fiber/v3"
1529)
1530
1531//go:embed *.go
1532var quine embed.FS
1533
1534var wasmsource = os.DirFS("wasm")
1535var coresource = os.DirFS("ui")
1536
1537func serveSourceCode(r *fiber.App) {
1538 r.Get("/sourcecodego", sourcecodego)
1539 r.Get("/sourcecodewasm", sourcecodewasm)
1540 r.Get("/sourcecodecore", sourcecodecore)
1541}
1542
1543func sourcecodego(c fiber.Ctx) error {
1544 return sourcecode(c, quine, "monokai")
1545}
1546func sourcecodewasm(c fiber.Ctx) error {
1547 return sourcecode(c, wasmsource, "dracula")
1548}
1549func sourcecodecore(c fiber.Ctx) error {
1550 return sourcecode(c, coresource, "solarized-dark256")
1551}
1552
1553func sourcecode(c fiber.Ctx, fsys fs.FS, styleName string) error {
1554 c.Set("Content-Type", "text/html;charset=utf-8")
1555 var buf bytes.Buffer
1556 var builder strings.Builder
1557
1558 fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
1559 if err != nil {
1560 return err
1561 }
1562 if !d.IsDir() && strings.HasSuffix(path, ".go") {
1563 content, err := fs.ReadFile(fsys, path)
1564 if err != nil {
1565 return err
1566 }
1567 builder.WriteString(fmt.Sprintf("// ===== %s =====\n", path))
1568 builder.Write(content)
1569 builder.WriteString("\n\n")
1570 }
1571 return nil
1572 })
1573
1574 // Pick lexer & style
1575 lexer := lexers.Get("go")
1576 if lexer == nil {
1577 lexer = lexers.Fallback
1578 }
1579 lexer = chroma.Coalesce(lexer)
1580
1581 style := styles.Get(styleName)
1582 if style == nil {
1583 style = styles.Fallback
1584 }
1585
1586 // Formatter with line numbers & CSS classes
1587 formatter := html.New(
1588 html.WithLineNumbers(true),
1589 html.WithClasses(true),
1590 )
1591
1592 iterator, err := lexer.Tokenise(nil, builder.String())
1593 if err != nil {
1594 return err
1595 }
1596
1597 // Optional: include CSS in output
1598 var css bytes.Buffer
1599 _ = formatter.WriteCSS(&css, style)
1600 buf.WriteString("<style>")
1601 buf.Write(css.Bytes())
1602 buf.WriteString("</style>")
1603
1604 if err := formatter.Format(&buf, style, iterator); err != nil {
1605 return err
1606 }
1607
1608 _, err = c.Status(fiber.StatusOK).Write(buf.Bytes())
1609 return err
1610}
1611
1612
1613// ===== tmpl.go =====
1614// Package main tmpl.go
1615package main
1616
1617import (
1618 "bytes"
1619 "fmt"
1620 htmpl "html/template"
1621 "log"
1622 "os"
1623 "path/filepath"
1624 "sort"
1625 "strconv"
1626 "strings"
1627 ttmpl "text/template"
1628 "time"
1629
1630 p "github.com/0magnet/m2/pkg/product"
1631 "github.com/gofiber/fiber/v3"
1632)
1633
1634/*
1635//go:embed htmpl/*
1636var templatesFS embed.FS
1637
1638//go:embed content/*
1639var contentFS embed.FS
1640*/
1641/*
1642var (
1643 templatesFS = os.DirFS("htmpl")
1644 contentFS = os.DirFS("content")
1645)
1646*/
1647/*
1648func mustReadEmbeddedFileToString(path string, fs embed.FS) string {
1649 return string(mustReadEmbeddedFileToBytes(path, fs))
1650}
1651
1652func mustReadEmbeddedFileToBytes(path string, fs embed.FS) []byte {
1653 data, err := fs.ReadFile(path)
1654 if err != nil {
1655 panic(err)
1656 }
1657 return data
1658}
1659*/
1660
1661func mustReadFileToString(path string) string {
1662 return string(mustReadFileToBytes(path))
1663}
1664
1665func mustReadFileToBytes(path string) []byte {
1666 data, err := os.ReadFile(path) //nolint
1667 if err != nil {
1668 panic(err)
1669 }
1670 return data
1671}
1672
1673type htmlTemplate struct {
1674 Head func() string
1675 Header func() string
1676 Categories func() string
1677 CatSubcats func() string
1678 Footer func() string
1679 MainPage func() string
1680 FrontPage func() string
1681 CategoryPage func() string
1682 ProductPage func() string
1683 ProductPageMD func() string
1684 Schema func() string
1685 Cart func() string
1686 XMLSitemap func() string
1687 Wasm func() string
1688 Clock func() string
1689 AboutPage func() string
1690 PolicyPage func() string
1691 LinksPage func() string
1692 CheckoutPage func() string
1693 CompletePage func() string
1694 CheckoutCSS func() string
1695 StyleCSS func() string
1696 COVIDPage func() string
1697}
1698
1699var h = htmlTemplate{
1700 Head: func() string { return mustReadFileToString("htmpl/head.html") },
1701 Header: func() string { return mustReadFileToString("htmpl/header.html") },
1702 Categories: func() string { return mustReadFileToString("htmpl/categories.html") },
1703 CatSubcats: func() string { return mustReadFileToString("htmpl/catsubcats.html") },
1704 Footer: func() string { return mustReadFileToString("htmpl/footer.html") },
1705 MainPage: func() string { return mustReadFileToString("htmpl/main.html") },
1706 FrontPage: func() string { return mustReadFileToString("htmpl/front.html") },
1707 CategoryPage: func() string { return mustReadFileToString("htmpl/category.html") },
1708 ProductPage: func() string { return mustReadFileToString("htmpl/product.html") },
1709 ProductPageMD: func() string { return mustReadFileToString("htmpl/product.md") },
1710 Schema: func() string { return mustReadFileToString("htmpl/schema.html") },
1711 Cart: func() string { return mustReadFileToString("htmpl/cart.html") },
1712 XMLSitemap: func() string { return mustReadFileToString("htmpl/sitemap.xml") },
1713 Wasm: func() string { return mustReadFileToString("htmpl/wasm.html") },
1714 Clock: func() string { return mustReadFileToString("htmpl/clock.html") },
1715 CompletePage: func() string { return mustReadFileToString("htmpl/complete.html") },
1716 AboutPage: func() string { return mustReadFileToString("content/about.html") },
1717 PolicyPage: func() string { return mustReadFileToString("content/policy.html") },
1718 LinksPage: func() string { return mustReadFileToString("content/links.html") },
1719 CheckoutPage: func() string { return mustReadFileToString("content/checkout.html") },
1720 CheckoutCSS: func() string { return mustReadFileToString("content/checkout.css") },
1721 StyleCSS: func() string { return mustReadFileToString("content/style.css") },
1722 COVIDPage: func() string { return mustReadFileToString("content/mementomori.html") },
1723}
1724
1725var htmlPageTemplateData htmlTemplateData
1726
1727var funcs = htmpl.FuncMap{
1728 "replace": replace, "mul": mul, "div": div, "safeHTML": safeHTML,
1729 "safeJS": safeJS, "stripProtocol": stripProtocol, "add": add, "sub": sub,
1730 "toFloat": toFloat, "equalsIgnoreCase": equalsIgnoreCase,
1731 "getsubcats": getsubcats, "escapesubcat": escapesubcat,
1732 "sortsubcats": sortsubcats, "repeat": repeat,
1733}
1734
1735func mainTmpl() (tmpl *htmpl.Template, err error) {
1736 tmpl = htmpl.New("index").Funcs(funcs)
1737 if _, err := tmpl.Parse(h.MainPage()); err != nil {
1738 log.Println("Error parsing index template:", err)
1739 return tmpl, err
1740 }
1741
1742 partials := []struct {
1743 Name string
1744 Content string
1745 }{
1746 {"head", h.Head()},
1747 {"schema", h.Schema()},
1748 {"header", h.Header()},
1749 {"catsubcats", h.CatSubcats()},
1750 {"categories", h.Categories()},
1751 {"footer", h.Footer()},
1752 {"cart", h.Cart()},
1753 {"wasm", h.Wasm()},
1754 }
1755
1756 for _, p := range partials {
1757 if _, err := tmpl.New(p.Name).Parse(p.Content); err != nil {
1758 log.Printf("Error parsing %s template: %v", p.Name, err)
1759 return tmpl, err
1760 }
1761 }
1762 return tmpl, err
1763}
1764
1765func pageMeta(c fiber.Ctx, base htmlTemplateData) htmlTemplateData {
1766 h := base
1767 host := string(c.Request().Host())
1768 proto := c.Protocol()
1769 h.Canonical = proto + "://" + host + c.OriginalURL()
1770 h.BaseURL = proto + "://" + host
1771 h.RequestHost = host
1772 h.Protocol = proto
1773 h.CatsCounts, h.Cats, h.SubCatsCounts, h.SubCatsByCat = getcategories(allproducts)
1774 h.LenAllProducts = len(allproducts)
1775 h.Time = time.Now().Format(time.RFC3339Nano)
1776 h.Year = fmt.Sprintf("%v", time.Now().Year())
1777 h.MetaDesc = f.Sitemeta
1778 h.KeyWords = strings.Replace(f.Sitelongname, " ", ", ", -1)
1779 return h
1780}
1781
1782func initTMPL() {
1783 htmlPageTemplateData = htmlTemplateData{
1784 TestMode: f.Teststripekey,
1785 Title: f.Sitelongname,
1786 StripePK: f.StripePK,
1787 SiteName: f.Sitedomain,
1788 SiteTagLine: f.Sitetagline,
1789 SiteName1: htmpl.HTML(checkerBoard(f.Sitedomain)), //nolint
1790 SiteLongName: f.Sitelongname,
1791 SiteASCIILogo: htmpl.HTML(f.SiteASCIILogo), //nolint
1792 SitePrettyName: f.Siteprettyname,
1793 SitePrettyNameCap: f.Siteprettynamecap,
1794 SitePrettyNameCaps: f.Siteprettynamecaps,
1795 TelegramContact: f.Tgcontact,
1796 TelegramChannel: f.Tgchannel,
1797 WasmExecPath: f.WasmExecPath,
1798 WasmExecRel: f.WasmExecPath,
1799 Cats: getcats(),
1800 LenAllProducts: len(allproducts),
1801 ImgSRC: func() (ret string) {
1802 ret = f.Siteimagesrc
1803 if ret == "" {
1804 ret = "/i"
1805 }
1806 return ret
1807 }(),
1808 Page: "front",
1809 Time: time.Now().Format(time.RFC3339Nano),
1810 Year: fmt.Sprintf("%v", time.Now().Year()),
1811 }
1812 htmlPageTemplateData.CatsCounts, htmlPageTemplateData.Cats, htmlPageTemplateData.SubCatsCounts, htmlPageTemplateData.SubCatsByCat = getcategories(allproducts)
1813 htmlPageTemplateData.WasmBinary = wasmBinary()
1814
1815}
1816
1817func wasmBinary() (ret []string) {
1818 if len(f.WasmSRC) == 0 {
1819 return ret
1820 }
1821 if f.UseTinygo {
1822 for _, wasmSRC := range f.WasmSRC {
1823 outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + "-tiny.wasm"
1824 ret = append(ret, outputFile)
1825 }
1826 return ret
1827 }
1828 for _, wasmSRC := range f.WasmSRC {
1829 outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + ".wasm"
1830 ret = append(ret, outputFile)
1831 }
1832 return ret
1833}
1834
1835type xmlTemplateData struct {
1836 Cats []string
1837 SubCatsByCat map[string][]string
1838 Products p.Products
1839 Update string
1840}
1841
1842func generateSitemapXML() string {
1843 xmlSitemapTemplateData := xmlTemplateData{
1844 Products: allproducts,
1845 Update: time.Now().Format("2006-01-02"),
1846 }
1847 _, xmlSitemapTemplateData.Cats, _, xmlSitemapTemplateData.SubCatsByCat = getcategories(allproducts)
1848 var err1 error
1849 xtmpl, err1 := ttmpl.New("index").Funcs(ttmpl.FuncMap{"getsubcats": getsubcats}).Parse(h.XMLSitemap())
1850 if err1 != nil {
1851 log.Println("Error parsing index template:", err1)
1852 }
1853 var result bytes.Buffer
1854 err1 = xtmpl.Execute(&result, xmlSitemapTemplateData)
1855 if err1 != nil {
1856 log.Println("error: ", err1)
1857 }
1858 return result.String()
1859}
1860
1861func toFloat(s string) float64 {
1862 if s == "" {
1863 return 0.0
1864 }
1865 f, err := strconv.ParseFloat(s, 64)
1866 if err != nil {
1867 return 0.0
1868 }
1869 return f
1870}
1871
1872func checkerBoard(input string) string {
1873 var result strings.Builder
1874 for i, char := range input {
1875 // Wrap every other letter with the specified HTML
1876 if i%2 == 0 {
1877 result.WriteString(fmt.Sprintf("<span class='nv'>%c</span>", char))
1878 } else {
1879 result.WriteRune(char)
1880 }
1881 }
1882 return result.String()
1883}
1884
1885type htmlTemplateData struct {
1886 Title string
1887 MetaDesc string
1888 Canonical string
1889 BaseURL string
1890 ImgSRC string // url where images are hosted
1891 OrdersURL string // url where checkout is served from
1892 SiteName string
1893 SiteTagLine string
1894 SiteName1 htmpl.HTML //checkerboard - alternate swap text & bg color
1895 SiteLongName string
1896 SitePrettyName string //ππππππ₯π π€π‘πππ£π.πππ₯
1897 SitePrettyNameCap string //ππππππ₯π π€π‘πππ£π.πππ₯
1898 SitePrettyNameCaps string //ππΈπΎβπΌπππββπΌβπΌ.βπΌπ
1899 SiteASCIILogo htmpl.HTML
1900 TelegramContact string
1901 TelegramChannel string
1902 Protocol string
1903 RequestHost string
1904 KeyWords string
1905 Style htmpl.HTML
1906 Heading htmpl.HTML
1907 StripePK string
1908 Cats []string
1909 CatsCounts map[string]int
1910 SubCatsCounts map[string]map[string]int
1911 SubCatsByCat map[string][]string
1912 LenAllProducts int
1913 Mobile bool
1914 Gocanvas htmpl.HTML
1915 WasmBinary []string
1916 WasmExecPath string
1917 WasmExecRel string
1918 StyleFontFace htmpl.CSS
1919 Message htmpl.HTML
1920 Page string
1921 Year string
1922 Time string
1923 AboutHTML htmpl.HTML
1924 LinksHTML htmpl.HTML
1925 PolicyHTML htmpl.HTML
1926 TestMode bool
1927}
1928
1929func equalsIgnoreCase(a, b string) bool {
1930 return strings.EqualFold(strings.Join(strings.Fields(a), ""), strings.Join(strings.Fields(b), ""))
1931}
1932
1933func replace(s, o, n string) string {
1934 return strings.ReplaceAll(s, o, n)
1935}
1936func mul(a, b float64) float64 {
1937 return a * b
1938}
1939func div(a, b float64) float64 {
1940 return a / b
1941}
1942func add(a, b int) int {
1943 return a + b
1944}
1945func sub(a, b int) int {
1946 return a - b
1947}
1948func safeHTML(s string) htmpl.HTML {
1949 return htmpl.HTML(s) //nolint
1950}
1951func safeJS(s string) htmpl.JS {
1952 return htmpl.JS(s) //nolint
1953}
1954func stripProtocol(s string) string {
1955 return strings.Replace(strings.Replace(s, "https://", "", -1), "http://", "", -1)
1956}
1957func repeat(s string, count int) string {
1958 var result string
1959 for i := 0; i < count; i++ {
1960 result += s
1961 }
1962 return result
1963}
1964func sortsubcats(subcats []string, counts map[string]map[string]int) []string {
1965 sort.Slice(subcats, func(i, j int) bool {
1966 catI, catJ := subcats[i], subcats[j]
1967 countI, countJ := counts[catI]["count"], counts[catJ]["count"]
1968 return countI > countJ
1969 })
1970 return subcats
1971}
1972
1973
1974// ===== wasm.go =====
1975// Package main wasm.go
1976package main
1977
1978import (
1979 "fmt"
1980 "log"
1981 "path/filepath"
1982 "strings"
1983 "time"
1984
1985 "github.com/bitfield/script"
1986 "github.com/briandowns/spinner"
1987)
1988
1989func compileWASM() {
1990 for _, wasmSRC := range f.WasmSRC {
1991 outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + ".wasm"
1992 compilecmd := fmt.Sprintf("bash -c 'time GOOS=js GOARCH=wasm %s %s -o %s %s && du -h %s'", f.Gobuild, ldflags(wasmSRC), outputFile, wasmSRC, outputFile)
1993 log.Println("Compiling wasm with:")
1994 log.Println(compilecmd)
1995 s := spinner.New(spinner.CharSets[14], 25*time.Millisecond)
1996 s.Suffix = " Compiling wasm..."
1997 s.Start()
1998 _, err := script.Exec(compilecmd).Stdout()
1999 if err != nil {
2000 log.Fatal(err)
2001 }
2002 s.Stop()
2003 log.Println("Compiled wasm!")
2004 }
2005 for _, wasmSRC := range f.WasmSRC {
2006 outputFile := strings.TrimSuffix(filepath.Base(wasmSRC), filepath.Ext(wasmSRC)) + "-tiny.wasm"
2007 compilecmd := fmt.Sprintf("bash -c 'time GOOS=js GOARCH=wasm %s %s -o %s %s && du -h %s'", f.Tinygobuild, ldflags(wasmSRC), outputFile, wasmSRC, outputFile)
2008 log.Println("compiling wasm with:")
2009 log.Println(compilecmd)
2010 s := spinner.New(spinner.CharSets[14], 25*time.Millisecond)
2011 s.Suffix = " Compiling wasm..."
2012 s.Start()
2013 _, err := script.Exec(compilecmd).Stdout()
2014 if err != nil {
2015 log.Fatal(err)
2016 }
2017 s.Stop()
2018 log.Println("Compiled wasm!")
2019 }
2020}
2021
2022
2023func ldflags(s string) (ss string) {
2024 if f.LDFlagsX != "" {
2025 res, err := script.File(s).Match(strings.Split(f.LDFlagsX, "=")[0]).String()
2026 if err != nil {
2027 panic(err)
2028 }
2029 if res != "" {
2030 ss += fmt.Sprintf(` -X 'main.%s' `, f.LDFlagsX)
2031 }
2032 }
2033 res, err := script.File(s).Match("wasmName").String()
2034 if err != nil {
2035 panic(err)
2036 }
2037 if res != "" {
2038 ss += fmt.Sprintf(` -X 'main.wasmName=%s' `, strings.TrimSuffix(filepath.Base(s), filepath.Ext(s))+".wasm")
2039 }
2040 if ss != "" {
2041 ss = `-ldflags="` + ss + `"`
2042 }
2043 return ss
2044}
2045
2046