summaryrefslogtreecommitdiff
path: root/main.go
blob: 5e837f6e872177f8cbcf53014168e41bd25ad594 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main

import (
	"flag"
	"fmt"
	"os"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"golang.org/x/net/html"
)

type Mode string

const (
	ModeQuery   Mode = "query"
	ModeReplace Mode = "replace"
	ModeAppend  Mode = "append"
	ModePrepend Mode = "prepend"
	ModeInvalid Mode = "invalid"

	FilenameStdin string = "-"
)

func main() {
	selector := flag.String("selector", "", "DOM query")
	mode := flag.String("mode", "query", "Operation mode: query, replace, append, prepend")
	filename := flag.String("filename", "", "Path to HTML file")
	writeData := flag.String("wdata", "", "Data to write when in non-query mode")
	flag.Parse()

	//args := flag.Args()

	parsedMode, err := parseMode(*mode)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to parse -mode: %s", err.Error())
		os.Exit(1)
	}

	doc, err := parseFilename(*filename)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to parse -filename: %s", err.Error())
		os.Exit(1)
	}

	switch parsedMode {
	case ModeQuery:
		if *selector == "" {
			// if selector is not specified return whole doc by default
			htmlStr, err := doc.Html()
			if err != nil {
				fmt.Fprintf(os.Stderr, "failed to construct HTML string: %s", err.Error())
				os.Exit(1)
			}
			fmt.Println(htmlStr)
		} else {
			doc.Find(*selector).Each(func(i int, s *goquery.Selection) {
				var buf strings.Builder
				err := html.Render(&buf, s.Get(0))
				if err != nil {
					fmt.Fprintf(os.Stderr, "failed to render node: %s", err.Error())
					os.Exit(1)
				}

				output := buf.String()
				fmt.Println(output)
			})
		}
	case ModeReplace:
		doc.Find(*selector).Each(func(i int, s *goquery.Selection) {
			s.ReplaceWithHtml(*writeData)
		})
	case ModeAppend:
		doc.Find(*selector).Each(func(i int, s *goquery.Selection) {
			s.AppendHtml(*writeData)
		})
	case ModePrepend:
		doc.Find(*selector).Each(func(i int, s *goquery.Selection) {
			s.PrependHtml(*writeData)
		})
	}

	if parsedMode != ModeQuery {
		// in non-query mode, print entire document back with modifications
		htmlStr, err := doc.Html()
		if err != nil {
			fmt.Fprintf(os.Stderr, "failed to render node: %s", err.Error())
			os.Exit(1)
		}
		fmt.Println(htmlStr)
	}
}

func parseMode(mode string) (Mode, error) {
	validModes := map[string]any{
		string(ModeQuery):   nil,
		string(ModeReplace): nil,
		string(ModeAppend):  nil,
		string(ModePrepend): nil,
	}

	if _, ok := validModes[mode]; !ok {
		return ModeInvalid, fmt.Errorf("invalid mode: %s", mode)
	}

	return Mode(mode), nil
}

func parseFilename(fileName string) (*goquery.Document, error) {
	var fd *os.File
	var err error

	if fileName == FilenameStdin {
		fd = os.Stdin
	} else {
		fd, err = os.Open(fileName)
		if err != nil {
			return nil, fmt.Errorf("error opening file: %s", err.Error())
		}
		defer fd.Close()
	}

	doc, err := goquery.NewDocumentFromReader(fd)
	if err != nil {
		return nil, fmt.Errorf("error parsing HTML: %s", err.Error())
	}

	return doc, nil
}