├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── print.go
└── print_test.go
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2018,
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTML Pretty Print for Go (golang)
2 | Package hpp (github.com/Joker/hpp) is a HTML formatter for Go.
3 |
4 |
5 | example:
6 | ```html
7 |
PugPug - template engine
11 |
You are amazing
Pug is a terse and simple templating
13 | language with a strong focus on
14 | performance and powerful features.
15 | ```
16 | becomes
17 | ```html
18 |
19 |
20 |
21 | Pug
22 |
27 |
28 |
29 | Pug - template engine
30 |
31 |
You are amazing
32 |
38 |
39 | Pug is a terse and simple templating
40 | language with a strong focus on
41 | performance and powerful features.
42 |
43 |
44 |
45 |
46 | ```
47 |
48 |
49 | ### Installation
50 |
51 | ```sh
52 | $ go get github.com/Joker/hpp
53 | ```
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/Joker/hpp
2 |
3 | go 1.12
4 |
5 | require golang.org/x/net v0.0.0-20190327091125-710a502c58a2
6 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2 | golang.org/x/net v0.0.0-20190327091125-710a502c58a2 h1:17UhVDrPb40BH5k6cyeb2V/7QlBNdo/a0+r0dtK+Utw=
3 | golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
5 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
6 |
--------------------------------------------------------------------------------
/print.go:
--------------------------------------------------------------------------------
1 | package hpp
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "log"
8 | "regexp"
9 | "strings"
10 | "unicode/utf8"
11 |
12 | "golang.org/x/net/html"
13 | )
14 |
15 | // TabStr - tab size
16 | var TabStr = []byte(" ")
17 |
18 | // NewStr should be "\n" or "\r\n" for windows
19 | var NewStr = []byte("\n")
20 |
21 | func isInline(tag []byte) bool {
22 | switch string(tag) {
23 | case "a_", "b", "i", "em", "strong", "code", "span", "ins",
24 | "big", "small", "tt", "abbr", "acronym", "cite", "dfn",
25 | "kbd", "samp", "var", "bdo", "map", "q", "sub", "sup":
26 | return true
27 | default:
28 | return false
29 | }
30 | }
31 |
32 | func isVoid(tag []byte) bool {
33 | switch string(tag) {
34 | case "input", "link", "meta", "hr", "img", "br", "area", "base", "col",
35 | "param", "command", "embed", "keygen", "source", "track", "wbr":
36 | return true
37 | default:
38 | return false
39 | }
40 | }
41 |
42 | func PrPrint(in string) string {
43 | out := new(bytes.Buffer)
44 | Format(strings.NewReader(in), out)
45 | return strings.TrimLeft(out.String(), "\n\r\t ")
46 | }
47 |
48 | func ByPrint(in []byte) []byte {
49 | out := new(bytes.Buffer)
50 | Format(bytes.NewReader(in), out)
51 | return bytes.TrimLeft(out.Bytes(), "\n\r\t ")
52 | }
53 |
54 | func Print(r io.Reader) []byte {
55 | out := new(bytes.Buffer)
56 | Format(r, out)
57 | return bytes.TrimLeft(out.Bytes(), "\n\r\t ")
58 | }
59 |
60 | func Format(r io.Reader, w io.Writer) {
61 | var (
62 | tokenize = html.NewTokenizer(r)
63 | depth = 0
64 | LongText = false
65 | prevType html.TokenType
66 | tagName []byte
67 | prvName []byte
68 | rb = regexp.MustCompile(`^\s+\S`)
69 | re = regexp.MustCompile(`\S\s+$`)
70 | )
71 | Loop:
72 | for {
73 | nowType := tokenize.Next()
74 |
75 | if nowType != html.TextToken {
76 | prvName = tagName
77 | tagName, _ = tokenize.TagName()
78 | }
79 |
80 | switch nowType {
81 | case html.StartTagToken:
82 | if !(isInline(tagName) && prevType == html.TextToken) {
83 | w.Write(NewStr)
84 | w.Write(bytes.Repeat(TabStr, depth))
85 | }
86 | w.Write(tokenize.Raw())
87 | if !isVoid(tagName) {
88 | depth += 1
89 | }
90 |
91 | case html.SelfClosingTagToken, html.CommentToken, html.DoctypeToken:
92 | w.Write(NewStr)
93 | w.Write(bytes.Repeat(TabStr, depth))
94 | w.Write(tokenize.Raw())
95 |
96 | case html.EndTagToken:
97 | if depth > 0 {
98 | depth -= 1
99 | }
100 | switch {
101 | case !bytes.Equal(prvName, tagName),
102 | prevType == html.SelfClosingTagToken,
103 | prevType == html.CommentToken,
104 | prevType == html.DoctypeToken,
105 | prevType == html.EndTagToken,
106 | prevType == html.TextToken && LongText:
107 |
108 | w.Write(NewStr)
109 | w.Write(bytes.Repeat(TabStr, depth))
110 | }
111 | w.Write(tokenize.Raw())
112 |
113 | case html.TextToken:
114 | t := bytes.Replace(tokenize.Raw(), []byte{'\t'}, TabStr, -1)
115 | text := bytes.Trim(t, "\n\r ")
116 | if re.Match(t) {
117 | text = append(text, ' ')
118 | }
119 | LongText = false
120 | if len(text) > 0 {
121 | if bytes.Contains(text, []byte{'\n'}) {
122 | if !(prevType == html.EndTagToken && isInline(tagName)) {
123 | w.Write(NewStr)
124 | w.Write(bytes.Repeat(TabStr, depth))
125 | } else {
126 | if rb.Match(t) {
127 | text = append([]byte{' '}, text...)
128 | }
129 | }
130 | w.Write(txtFmt(text, depth))
131 | LongText = true
132 | } else {
133 | switch {
134 | case utf8.RuneCount(text) > 80, prevType != html.StartTagToken:
135 |
136 | if !(prevType == html.EndTagToken && isInline(tagName)) {
137 | w.Write(NewStr)
138 | w.Write(bytes.Repeat(TabStr, depth))
139 | LongText = true
140 | } else {
141 | if rb.Match(t) {
142 | text = append([]byte{' '}, text...)
143 | }
144 | }
145 | }
146 | w.Write(text)
147 | }
148 | }
149 |
150 | case html.ErrorToken:
151 | err := tokenize.Err()
152 | if err.Error() == "EOF" {
153 | break Loop
154 | }
155 | log.Panicln(err)
156 |
157 | }
158 | prevType = nowType
159 | }
160 | w.Write(NewStr)
161 | }
162 |
163 | func PHPformat(in []byte) []byte {
164 | in = bytes.Replace(in, []byte(""), []byte("{{{"), -1)
165 | in = bytes.Replace(in, []byte("?>"), []byte("}}}"), -1)
166 |
167 | wr := new(bytes.Buffer)
168 | Format(bytes.NewReader(in), wr)
169 |
170 | out := bytes.Replace(wr.Bytes(), []byte("{{{"), []byte(""), -1)
171 | out = bytes.Replace(out, []byte("}}}"), []byte("?>"), -1)
172 | return bytes.TrimLeft(out, "\n\r\t ")
173 | }
174 |
175 | func txtFmt(txt []byte, depth int) []byte {
176 | var (
177 | min = 1000
178 | ln = 0
179 | f = func(c rune) bool { return '\n' != c && ' ' != c }
180 | )
181 | for _, v := range bytes.FieldsFunc(txt, f) {
182 | ln = len(bytes.TrimLeft(v, " "))
183 | if ln > 0 && ln < min {
184 | min = ln
185 | }
186 | }
187 | var re = regexp.MustCompile(fmt.Sprintf(`\n\s{%d}`, min-1))
188 | return re.ReplaceAllLiteral(txt, append([]byte{'\n'}, bytes.Repeat(TabStr, depth)...))
189 | }
190 |
--------------------------------------------------------------------------------
/print_test.go:
--------------------------------------------------------------------------------
1 | package hpp
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strings"
7 | "testing"
8 | )
9 |
10 | var input = `PugPug - template engine
14 |
You are amazing
Pug is a terse and simple templating
16 | language with a strong focus on
17 | performance and powerful features.
`
18 |
19 | var expect = `
20 |
21 |
22 | Pug
23 |
28 |
29 |
30 | Pug - template engine
31 |
32 |
You are amazing
33 |
39 |
40 | Pug is a terse and simple templating
41 | language with a strong focus on
42 | performance and powerful features.
43 |
44 |
45 |
46 |
47 | `
48 |
49 | func TestFormat(t *testing.T) {
50 | bf := new(bytes.Buffer)
51 | Format(strings.NewReader(input), bf)
52 | output := bf.String()
53 | if expect != strings.TrimLeft(output, "\n\r\t ") {
54 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output)
55 | }
56 | }
57 |
58 | func TestPrint(t *testing.T) {
59 | output := Print(strings.NewReader(input))
60 | if expect != string(output) {
61 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output)
62 | }
63 | }
64 |
65 | func TestByPrint(t *testing.T) {
66 | output := ByPrint([]byte(input))
67 | if expect != string(output) {
68 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output)
69 | }
70 | }
71 |
72 | func TestPrPrint(t *testing.T) {
73 | output := PrPrint(input)
74 | if expect != output {
75 | t.Errorf("\n------ expect:\n%s\n------ output:\n%s", expect, output)
76 | } else {
77 | fmt.Print(output)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------