+71
.github/workflows/reproducible-builds.yaml
+71
.github/workflows/reproducible-builds.yaml
···
···
1
+
name: Reproducible builds
2
+
3
+
on:
4
+
push:
5
+
branches: ["main"]
6
+
pull_request:
7
+
branches: ["main"]
8
+
9
+
permissions:
10
+
contents: read
11
+
12
+
jobs:
13
+
reproducible:
14
+
runs-on: ubuntu-latest
15
+
steps:
16
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17
+
with:
18
+
persist-credentials: false
19
+
fetch-tags: true
20
+
21
+
- name: Setup Go environment
22
+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
23
+
with:
24
+
go-version: "stable"
25
+
26
+
- name: Setup Golang caches
27
+
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
28
+
with:
29
+
path: |
30
+
~/.cache/go-build
31
+
~/go/pkg/mod
32
+
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
33
+
restore-keys: |
34
+
${{ runner.os }}-golang-
35
+
36
+
- name: Setup Python environment
37
+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
38
+
with:
39
+
python-version: "3.12"
40
+
41
+
- name: Install diffoscope
42
+
run: |
43
+
pip install diffoscope==297
44
+
45
+
sudo apt-get update
46
+
sudo apt-get -y install 7zip abootimg acl apksigcopier apksigner apktool binutils-multiarch black bzip2 caca-utils colord db-util default-jdk default-jdk-headless device-tree-compiler docx2txt e2fsprogs enjarify ffmpeg file fontforge-extras fonttools fp-utils genisoimage gettext ghc ghostscript giflib-tools gnumeric gnupg-utils gpg hdf5-tools html2text imagemagick openjdk-21-jdk jsbeautifier libarchive-tools linux-image-generic llvm lz4 lzip mono-utils ocaml-nox odt2txt oggvideotools openssh-client openssl perl pgpdump poppler-utils procyon-decompiler python3-all python3-argcomplete python3-binwalk python3-debian python3-defusedxml python3-distro python3-guestfs python3-h5py python3-jsondiff python3-pdfminer python3-progressbar python3-pytest python3-pyxattr python3-rpm python3-tlsh r-base-core rpm2cpio sng sqlite3 squashfs-tools tcpdump u-boot-tools unzip wabt xmlbeans xxd xz-utils zip zstd
47
+
48
+
- name: Build yeet packages twice
49
+
run: |
50
+
mkdir -p ./var/pass1 ./var/pass2 ./var/output
51
+
go run ./cmd/yeet --force-git-version 1.0.0 --package-dest-dir ./var/pass1
52
+
go run ./cmd/yeet --force-git-version 1.0.0 --package-dest-dir ./var/pass2
53
+
54
+
for file in ./var/pass1/*; do
55
+
diffoscope --text "${file/pass1/output}.txt" $file "${file/pass1/pass2}";
56
+
done
57
+
58
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
59
+
with:
60
+
name: pass1
61
+
path: var/pass1/*
62
+
63
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
64
+
with:
65
+
name: pass2
66
+
path: var/pass2/*
67
+
68
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
69
+
with:
70
+
name: output
71
+
path: var/output/*
+2
-2
.github/workflows/zizmor.yml
+2
-2
.github/workflows/zizmor.yml
···
21
persist-credentials: false
22
23
- name: Install the latest version of uv
24
-
uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6.0.0
25
26
- name: Run zizmor ๐
27
run: uvx zizmor --format sarif . > results.sarif
···
29
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30
31
- name: Upload SARIF file
32
-
uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
33
with:
34
sarif_file: results.sarif
35
category: zizmor
···
21
persist-credentials: false
22
23
- name: Install the latest version of uv
24
+
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
25
26
- name: Run zizmor ๐
27
run: uvx zizmor --format sarif . > results.sarif
···
29
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30
31
- name: Upload SARIF file
32
+
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
33
with:
34
sarif_file: results.sarif
35
category: zizmor
+50
CHANGELOG.md
+50
CHANGELOG.md
···
1
+
## [0.6.1](https://github.com/TecharoHQ/yeet/compare/v0.6.0...v0.6.1) (2025-06-03)
2
+
3
+
4
+
### Bug Fixes
5
+
6
+
* **internal/mkrpm:** ensure greater reproduciblity ([f515b41](https://github.com/TecharoHQ/yeet/commit/f515b4130e9727b1acb23701dc26b457e4517949))
7
+
* **internal/vfs:** don't give archive/tar Sys data ([b68cbc8](https://github.com/TecharoHQ/yeet/commit/b68cbc825e7597509e0b8f595f6bc743a2b2ea5b))
8
+
* **internal/vfs:** implement gname and uname for modtimefileinfo ([18f16f2](https://github.com/TecharoHQ/yeet/commit/18f16f2d0e1b9f924097733eb799c9d8e278e28a))
9
+
10
+
# [0.6.0](https://github.com/TecharoHQ/yeet/compare/v0.5.0...v0.6.0) (2025-06-01)
11
+
12
+
13
+
### Bug Fixes
14
+
15
+
* use sh instead of bash ([#27](https://github.com/TecharoHQ/yeet/issues/27)) ([2e169ef](https://github.com/TecharoHQ/yeet/commit/2e169efafec110d71c8fb596c0a163f902c6f757))
16
+
17
+
18
+
### Features
19
+
20
+
* support SOURCE_DATE_EPOCH ([#26](https://github.com/TecharoHQ/yeet/issues/26)) ([c01c3db](https://github.com/TecharoHQ/yeet/commit/c01c3db54cddbed5faf1f32a0993620c7aaade9b))
21
+
* use mvdan.cc/sh/v3 instead of system sh ([#28](https://github.com/TecharoHQ/yeet/issues/28)) ([5459d92](https://github.com/TecharoHQ/yeet/commit/5459d922e1e3e0aaa3deef6cdf449c4cab3aeeea))
22
+
23
+
# [0.5.0](https://github.com/TecharoHQ/yeet/compare/v0.4.0...v0.5.0) (2025-05-30)
24
+
25
+
26
+
### Features
27
+
28
+
* **deb,rpm,tarball:** implement reproducible builds ([49686d8](https://github.com/TecharoHQ/yeet/commit/49686d84f20a6df92378139a6705504621f7c9d9))
29
+
30
+
# [0.4.0](https://github.com/TecharoHQ/yeet/compare/v0.3.0...v0.4.0) (2025-05-29)
31
+
32
+
33
+
### Features
34
+
35
+
* **yeetfile:** ppc64le builds ([bbc1e38](https://github.com/TecharoHQ/yeet/commit/bbc1e384f82724660365a4525262180241ec3f06))
36
+
37
+
# [0.3.0](https://github.com/TecharoHQ/yeet/compare/v0.2.3...v0.3.0) (2025-05-20)
38
+
39
+
40
+
### Features
41
+
42
+
* **confyg:** export package publicly ([7abba3a](https://github.com/TecharoHQ/yeet/commit/7abba3a1ddcdd9eca4776a80d98851dcfc5005fc))
43
+
44
+
## [0.2.3](https://github.com/TecharoHQ/yeet/compare/v0.2.2...v0.2.3) (2025-05-09)
45
+
46
+
47
+
### Bug Fixes
48
+
49
+
* **cmd/yeet:** show build method in --version ([#20](https://github.com/TecharoHQ/yeet/issues/20)) ([ca06ce7](https://github.com/TecharoHQ/yeet/commit/ca06ce7d9247e1d18b8be346e191404e652bd6f9))
50
+
51
## [0.2.2](https://github.com/TecharoHQ/yeet/compare/v0.2.1...v0.2.2) (2025-05-02)
52
53
+12
README.md
+12
README.md
···
57
## Support
58
59
For support, please [subscribe to me on Patreon](https://patreon.com/cadey) and ask in the `#yeet` channel in the patron Discord.
60
+
61
+
## Packaging Status
62
+
63
+
[](https://repology.org/project/yeet-js-build-tool/versions)
64
+
65
+
## Contributors
66
+
67
+
<a href="https://github.com/TecharoHQ/yeet/graphs/contributors">
68
+
<img src="https://contrib.rocks/image?repo=TecharoHQ/yeet" />
69
+
</a>
70
+
71
+
Made with [contrib.rocks](https://contrib.rocks).
+36
-12
cmd/yeet/main.go
+36
-12
cmd/yeet/main.go
···
1
package main
2
3
import (
4
"context"
5
"flag"
6
"fmt"
···
8
"log/slog"
9
"net/http"
10
"os"
11
-
"os/exec"
12
"path/filepath"
13
"runtime"
14
"strings"
15
16
"al.essio.dev/pkg/shellescape"
17
yeetver "github.com/TecharoHQ/yeet"
18
-
"github.com/TecharoHQ/yeet/internal/confyg/flagconfyg"
19
"github.com/TecharoHQ/yeet/internal/gitea"
20
"github.com/TecharoHQ/yeet/internal/mkdeb"
21
"github.com/TecharoHQ/yeet/internal/mkrpm"
···
23
"github.com/TecharoHQ/yeet/internal/pkgmeta"
24
"github.com/TecharoHQ/yeet/internal/yeet"
25
"github.com/dop251/goja"
26
)
27
28
var (
···
74
75
func buildShellCommand(literals []string, exprs ...any) string {
76
var sb strings.Builder
77
for i, value := range exprs {
78
sb.WriteString(literals[i])
79
sb.WriteString(shellescape.Quote(fmt.Sprint(value)))
···
84
return sb.String()
85
}
86
87
-
func runShellCommand(literals []string, exprs ...any) string {
88
-
shPath, err := exec.LookPath("bash")
89
if err != nil {
90
-
panic(err)
91
}
92
93
-
cmd := buildShellCommand(literals, exprs...)
94
95
-
slog.Debug("running command", "cmd", cmd)
96
-
output, err := yeet.Output(context.Background(), shPath, "-c", cmd)
97
if err != nil {
98
-
panic(err)
99
}
100
101
-
return output
102
}
103
104
func hostname() string {
···
127
flag.Parse()
128
129
if *version {
130
-
fmt.Printf("yeet version %s\n", yeetver.Version)
131
return
132
}
133
···
146
log.Fatal(err)
147
}
148
149
-
vm.Set("$", runShellCommand)
150
151
vm.Set("deb", map[string]any{
152
"build": func(p pkgmeta.Package) string {
···
156
}
157
return foutpath
158
},
159
})
160
161
vm.Set("docker", map[string]any{
···
207
}
208
return foutpath
209
},
210
})
211
212
vm.Set("tarball", map[string]any{
···
217
}
218
return foutpath
219
},
220
})
221
222
vm.Set("yeet", map[string]any{
···
1
package main
2
3
import (
4
+
"bytes"
5
"context"
6
"flag"
7
"fmt"
···
9
"log/slog"
10
"net/http"
11
"os"
12
"path/filepath"
13
"runtime"
14
"strings"
15
16
"al.essio.dev/pkg/shellescape"
17
yeetver "github.com/TecharoHQ/yeet"
18
+
"github.com/TecharoHQ/yeet/confyg/flagconfyg"
19
"github.com/TecharoHQ/yeet/internal/gitea"
20
"github.com/TecharoHQ/yeet/internal/mkdeb"
21
"github.com/TecharoHQ/yeet/internal/mkrpm"
···
23
"github.com/TecharoHQ/yeet/internal/pkgmeta"
24
"github.com/TecharoHQ/yeet/internal/yeet"
25
"github.com/dop251/goja"
26
+
"mvdan.cc/sh/v3/interp"
27
+
"mvdan.cc/sh/v3/syntax"
28
)
29
30
var (
···
76
77
func buildShellCommand(literals []string, exprs ...any) string {
78
var sb strings.Builder
79
+
fmt.Fprintln(&sb, "set -e")
80
+
81
for i, value := range exprs {
82
sb.WriteString(literals[i])
83
sb.WriteString(shellescape.Quote(fmt.Sprint(value)))
···
88
return sb.String()
89
}
90
91
+
func runShellCommand(ctx context.Context, literals []string, exprs ...any) (string, error) {
92
+
src := buildShellCommand(literals, exprs...)
93
+
94
+
slog.Debug("running command", "src", src)
95
+
96
+
file, err := syntax.NewParser().Parse(strings.NewReader(src), "")
97
if err != nil {
98
+
return "", err
99
}
100
101
+
var buf bytes.Buffer
102
103
+
runner, err := interp.New(
104
+
interp.StdIO(nil, &buf, os.Stderr),
105
+
)
106
if err != nil {
107
+
return "", err
108
+
}
109
+
110
+
if err := runner.Run(ctx, file); err != nil {
111
+
return "", err
112
}
113
114
+
slog.Debug("command output", "src", src, "output", buf.String())
115
+
116
+
return buf.String(), nil
117
}
118
119
func hostname() string {
···
142
flag.Parse()
143
144
if *version {
145
+
fmt.Printf("yeet version %s, built via %s\n", yeetver.Version, yeetver.BuildMethod)
146
return
147
}
148
···
161
log.Fatal(err)
162
}
163
164
+
vm.Set("$", func(literals []string, exprs ...any) string {
165
+
result, err := runShellCommand(ctx, literals, exprs...)
166
+
if err != nil {
167
+
panic(err)
168
+
}
169
+
return result
170
+
})
171
172
vm.Set("deb", map[string]any{
173
"build": func(p pkgmeta.Package) string {
···
177
}
178
return foutpath
179
},
180
+
"name": "debian",
181
})
182
183
vm.Set("docker", map[string]any{
···
229
}
230
return foutpath
231
},
232
+
"name": "rpm",
233
})
234
235
vm.Set("tarball", map[string]any{
···
240
}
241
return foutpath
242
},
243
+
"name": "tarball",
244
})
245
246
vm.Set("yeet", map[string]any{
+27
confyg/LICENSE
+27
confyg/LICENSE
···
···
1
+
Copyright (c) 2009 The Go Authors. All rights reserved.
2
+
3
+
Redistribution and use in source and binary forms, with or without
4
+
modification, are permitted provided that the following conditions are
5
+
met:
6
+
7
+
* Redistributions of source code must retain the above copyright
8
+
notice, this list of conditions and the following disclaimer.
9
+
* Redistributions in binary form must reproduce the above
10
+
copyright notice, this list of conditions and the following disclaimer
11
+
in the documentation and/or other materials provided with the
12
+
distribution.
13
+
* Neither the name of Google Inc. nor the names of its
14
+
contributors may be used to endorse or promote products derived from
15
+
this software without specific prior written permission.
16
+
17
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+109
confyg/README.md
+109
confyg/README.md
···
···
1
+
# confyg
2
+
3
+
A suitably generic form of the Go module configuration file parser.
4
+
5
+
[](https://godoc.org/within.website/confyg)
6
+
7
+
Usage is simple:
8
+
9
+
```go
10
+
type server struct {
11
+
port string
12
+
keys *crypto.Keypair
13
+
db *storm.DB
14
+
}
15
+
16
+
func (s *server) Allow(verb string, block bool) bool {
17
+
switch verb {
18
+
case "port":
19
+
return !block
20
+
case "dbfile":
21
+
return !block
22
+
case "keys":
23
+
return !block
24
+
}
25
+
26
+
return false
27
+
}
28
+
29
+
func (s *server) Read(errs *bytes.Buffer, fs *confyg.FileSyntax, line *confyg.Line, verb string, args []string) {
30
+
switch verb {
31
+
case "port":
32
+
_, err := strconv.Atoi(args[0])
33
+
if err != nil {
34
+
fmt.Fprintf(errs, "%s:%d value is not a number: %s: %v\n", fs.Name, line.Start.Line, args[0], err)
35
+
return
36
+
}
37
+
38
+
s.port = args[0]
39
+
40
+
case "dbfile":
41
+
dbFile := args[0][1 : len(args[0])-1] // shuck off quotes
42
+
43
+
db, err := storm.Open(dbFile)
44
+
if err != nil {
45
+
fmt.Fprintf(errs, "%s:%d failed to open storm database: %s: %v\n", fs.Name, line.Start.Line, args[0], err)
46
+
return
47
+
}
48
+
49
+
s.db = db
50
+
51
+
case "keys":
52
+
kp := &crypto.Keypair{}
53
+
54
+
pubk, err := hex.DecodeString(args[0])
55
+
if err != nil {
56
+
fmt.Fprintf(errs, "%s:%d invalid public key: %v\n", fs.Name, line.Start.Line, err)
57
+
return
58
+
}
59
+
60
+
privk, err := hex.DecodeString(args[1])
61
+
if err != nil {
62
+
fmt.Fprintf(errs, "%s:%d invalid private key: %v\n", fs.Name, line.Start.Line, err)
63
+
return
64
+
}
65
+
66
+
copy(kp.Public[:], pubk[0:32])
67
+
copy(kp.Private[:], privk[0:32])
68
+
69
+
s.keys = kp
70
+
}
71
+
}
72
+
73
+
var (
74
+
configFile = flag.String("cfg", "./apig.cfg", "apig config file location")
75
+
)
76
+
77
+
func main() {
78
+
flag.Parse()
79
+
80
+
data, err := ioutil.ReadFile(*configFile)
81
+
if err != nil {
82
+
log.Fatal(err)
83
+
}
84
+
85
+
s := &server{}
86
+
_, err = confyg.Parse(*configFile, data, s, s)
87
+
if err != nil {
88
+
log.Fatal(err)
89
+
}
90
+
91
+
_ = s
92
+
}
93
+
```
94
+
95
+
Or use [`flagconfyg`](https://godoc.org/within.website/confyg/flagconfyg):
96
+
97
+
```go
98
+
var (
99
+
config = flag.Config("cfg", "", "if set, configuration file to load (see https://github.com/Xe/x/blob/master/docs/man/flagconfyg.5)")
100
+
)
101
+
102
+
func main() {
103
+
flag.Parse()
104
+
105
+
if *config != "" {
106
+
flagconfyg.CmdParse(*config)
107
+
}
108
+
}
109
+
```
+19
confyg/allower.go
+19
confyg/allower.go
···
···
1
+
package confyg
2
+
3
+
// Allower defines if a given verb and block combination is valid for
4
+
// configuration parsing.
5
+
//
6
+
// If this is intended to be a statement-like verb, block should be set
7
+
// to false. If this is intended to be a block-like verb, block should
8
+
// be set to true.
9
+
type Allower interface {
10
+
Allow(verb string, block bool) bool
11
+
}
12
+
13
+
// AllowerFunc implements Allower for inline definitions.
14
+
type AllowerFunc func(verb string, block bool) bool
15
+
16
+
// Allow implements Allower.
17
+
func (a AllowerFunc) Allow(verb string, block bool) bool {
18
+
return a(verb, block)
19
+
}
+48
confyg/allower_test.go
+48
confyg/allower_test.go
···
···
1
+
package confyg
2
+
3
+
import (
4
+
"fmt"
5
+
"testing"
6
+
)
7
+
8
+
func TestAllower(t *testing.T) {
9
+
al := AllowerFunc(func(verb string, block bool) bool {
10
+
switch verb {
11
+
case "project":
12
+
if block {
13
+
return false
14
+
}
15
+
16
+
return true
17
+
}
18
+
19
+
return false
20
+
})
21
+
22
+
cases := []struct {
23
+
verb string
24
+
block bool
25
+
want bool
26
+
}{
27
+
{
28
+
verb: "project",
29
+
block: false,
30
+
want: true,
31
+
},
32
+
{
33
+
verb: "nonsense",
34
+
block: true,
35
+
want: false,
36
+
},
37
+
}
38
+
39
+
for _, cs := range cases {
40
+
t.Run(fmt.Sprint(cs), func(t *testing.T) {
41
+
result := al.Allow(cs.verb, cs.block)
42
+
43
+
if result != cs.want {
44
+
t.Fatalf("wanted Allow(%q, %v) == %v, got: %v", cs.verb, cs.block, cs.want, result)
45
+
}
46
+
})
47
+
}
48
+
}
+46
confyg/flagconfyg/flagconfyg.go
+46
confyg/flagconfyg/flagconfyg.go
···
···
1
+
// Package flagconfyg is a hack around confyg. This will blindly convert config
2
+
// verbs to flag values.
3
+
package flagconfyg
4
+
5
+
import (
6
+
"bytes"
7
+
"context"
8
+
"flag"
9
+
"log"
10
+
"os"
11
+
"strings"
12
+
13
+
"github.com/TecharoHQ/yeet/confyg"
14
+
)
15
+
16
+
// CmdParse is a quick wrapper for command usage. It explodes on errors.
17
+
func CmdParse(ctx context.Context, path string) {
18
+
data, err := os.ReadFile(path)
19
+
if err != nil {
20
+
return
21
+
}
22
+
23
+
err = Parse(path, data, flag.CommandLine)
24
+
if err != nil {
25
+
log.Printf("can't parse %s: %v", path, err)
26
+
return
27
+
}
28
+
}
29
+
30
+
// Parse parses the config file in the given file by name, bytes data and into
31
+
// the given flagset.
32
+
func Parse(name string, data []byte, fs *flag.FlagSet) error {
33
+
lineRead := func(errs *bytes.Buffer, fs_ *confyg.FileSyntax, line *confyg.Line, verb string, args []string) {
34
+
err := fs.Set(verb, strings.Join(args, " "))
35
+
if err != nil {
36
+
errs.WriteString(err.Error())
37
+
}
38
+
}
39
+
40
+
_, err := confyg.Parse(name, data, confyg.ReaderFunc(lineRead), confyg.AllowerFunc(allower))
41
+
return err
42
+
}
43
+
44
+
func allower(verb string, block bool) bool {
45
+
return true
46
+
}
+31
confyg/flagconfyg/flagconfyg_test.go
+31
confyg/flagconfyg/flagconfyg_test.go
···
···
1
+
package flagconfyg
2
+
3
+
import (
4
+
"flag"
5
+
"testing"
6
+
)
7
+
8
+
func TestFlagConfyg(t *testing.T) {
9
+
fs := flag.NewFlagSet("test", flag.PanicOnError)
10
+
sc := fs.String("subscribe", "", "to pewdiepie")
11
+
us := fs.String("unsubscribe", "all the time", "from t-series")
12
+
13
+
const configFile = `subscribe pewdiepie
14
+
15
+
unsubscribe (
16
+
t-series
17
+
)`
18
+
19
+
err := Parse("test.cfg", []byte(configFile), fs)
20
+
if err != nil {
21
+
t.Fatal(err)
22
+
}
23
+
24
+
if *sc != "pewdiepie" {
25
+
t.Errorf("wanted subscribe->pewdiepie, got: %s", *sc)
26
+
}
27
+
28
+
if *us != "t-series" {
29
+
t.Errorf("wanted unsubscribe->t-series, got: %s", *us)
30
+
}
31
+
}
+18
confyg/map_output.go
+18
confyg/map_output.go
···
···
1
+
package confyg
2
+
3
+
import (
4
+
"bytes"
5
+
"strings"
6
+
)
7
+
8
+
// MapConfig is a simple wrapper around a map.
9
+
type MapConfig map[string][]string
10
+
11
+
// Allow accepts everything.
12
+
func (mc MapConfig) Allow(verb string, block bool) bool {
13
+
return true
14
+
}
15
+
16
+
func (mc MapConfig) Read(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string) {
17
+
mc[verb] = append(mc[verb], strings.Join(args, " "))
18
+
}
+26
confyg/map_output_test.go
+26
confyg/map_output_test.go
···
···
1
+
package confyg
2
+
3
+
import "testing"
4
+
5
+
func TestMapConfig(t *testing.T) {
6
+
mc := MapConfig{}
7
+
8
+
const configFile = `subscribe pewdiepie
9
+
10
+
unsubscribe (
11
+
t-series
12
+
)`
13
+
14
+
_, err := Parse("test.cfg", []byte(configFile), mc, mc)
15
+
if err != nil {
16
+
t.Fatal(err)
17
+
}
18
+
19
+
if mc["subscribe"][0] != "pewdiepie" {
20
+
t.Errorf("wanted subscribe->pewdiepie, got: %s", mc["subscribe"][0])
21
+
}
22
+
23
+
if mc["unsubscribe"][0] != "t-series" {
24
+
t.Errorf("wanted unsubscribe->t-series, got: %s", mc["unsubscribe"][0])
25
+
}
26
+
}
+164
confyg/print.go
+164
confyg/print.go
···
···
1
+
// Copyright 2018 The Go Authors. All rights reserved.
2
+
// Use of this source code is governed by a BSD-style
3
+
// license that can be found in the LICENSE file.
4
+
5
+
// Module file printer.
6
+
7
+
package confyg
8
+
9
+
import (
10
+
"bytes"
11
+
"fmt"
12
+
"strings"
13
+
)
14
+
15
+
func Format(f *FileSyntax) []byte {
16
+
pr := &printer{}
17
+
pr.file(f)
18
+
return pr.Bytes()
19
+
}
20
+
21
+
// A printer collects the state during printing of a file or expression.
22
+
type printer struct {
23
+
bytes.Buffer // output buffer
24
+
comment []Comment // pending end-of-line comments
25
+
margin int // left margin (indent), a number of tabs
26
+
}
27
+
28
+
// printf prints to the buffer.
29
+
func (p *printer) printf(format string, args ...interface{}) {
30
+
fmt.Fprintf(p, format, args...)
31
+
}
32
+
33
+
// indent returns the position on the current line, in bytes, 0-indexed.
34
+
func (p *printer) indent() int {
35
+
b := p.Bytes()
36
+
n := 0
37
+
for n < len(b) && b[len(b)-1-n] != '\n' {
38
+
n++
39
+
}
40
+
return n
41
+
}
42
+
43
+
// newline ends the current line, flushing end-of-line comments.
44
+
func (p *printer) newline() {
45
+
if len(p.comment) > 0 {
46
+
p.printf(" ")
47
+
for i, com := range p.comment {
48
+
if i > 0 {
49
+
p.trim()
50
+
p.printf("\n")
51
+
for i := 0; i < p.margin; i++ {
52
+
p.printf("\t")
53
+
}
54
+
}
55
+
p.printf("%s", strings.TrimSpace(com.Token))
56
+
}
57
+
p.comment = p.comment[:0]
58
+
}
59
+
60
+
p.trim()
61
+
p.printf("\n")
62
+
for i := 0; i < p.margin; i++ {
63
+
p.printf("\t")
64
+
}
65
+
}
66
+
67
+
// trim removes trailing spaces and tabs from the current line.
68
+
func (p *printer) trim() {
69
+
// Remove trailing spaces and tabs from line we're about to end.
70
+
b := p.Bytes()
71
+
n := len(b)
72
+
for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') {
73
+
n--
74
+
}
75
+
p.Truncate(n)
76
+
}
77
+
78
+
// file formats the given file into the print buffer.
79
+
func (p *printer) file(f *FileSyntax) {
80
+
for _, com := range f.Before {
81
+
p.printf("%s", strings.TrimSpace(com.Token))
82
+
p.newline()
83
+
}
84
+
85
+
for i, stmt := range f.Stmt {
86
+
switch x := stmt.(type) {
87
+
case *CommentBlock:
88
+
// comments already handled
89
+
p.expr(x)
90
+
91
+
default:
92
+
p.expr(x)
93
+
p.newline()
94
+
}
95
+
96
+
for _, com := range stmt.Comment().After {
97
+
p.printf("%s", strings.TrimSpace(com.Token))
98
+
p.newline()
99
+
}
100
+
101
+
if i+1 < len(f.Stmt) {
102
+
p.newline()
103
+
}
104
+
}
105
+
}
106
+
107
+
func (p *printer) expr(x Expr) {
108
+
// Emit line-comments preceding this expression.
109
+
if before := x.Comment().Before; len(before) > 0 {
110
+
// Want to print a line comment.
111
+
// Line comments must be at the current margin.
112
+
p.trim()
113
+
if p.indent() > 0 {
114
+
// There's other text on the line. Start a new line.
115
+
p.printf("\n")
116
+
}
117
+
// Re-indent to margin.
118
+
for i := 0; i < p.margin; i++ {
119
+
p.printf("\t")
120
+
}
121
+
for _, com := range before {
122
+
p.printf("%s", strings.TrimSpace(com.Token))
123
+
p.newline()
124
+
}
125
+
}
126
+
127
+
switch x := x.(type) {
128
+
default:
129
+
panic(fmt.Errorf("printer: unexpected type %T", x))
130
+
131
+
case *CommentBlock:
132
+
// done
133
+
134
+
case *LParen:
135
+
p.printf("(")
136
+
case *RParen:
137
+
p.printf(")")
138
+
139
+
case *Line:
140
+
sep := ""
141
+
for _, tok := range x.Token {
142
+
p.printf("%s%s", sep, tok)
143
+
sep = " "
144
+
}
145
+
146
+
case *LineBlock:
147
+
for _, tok := range x.Token {
148
+
p.printf("%s ", tok)
149
+
}
150
+
p.expr(&x.LParen)
151
+
p.margin++
152
+
for _, l := range x.Line {
153
+
p.newline()
154
+
p.expr(l)
155
+
}
156
+
p.margin--
157
+
p.newline()
158
+
p.expr(&x.RParen)
159
+
}
160
+
161
+
// Queue end-of-line comments for printing when we
162
+
// reach the end of the line.
163
+
p.comment = append(p.comment, x.Comment().Suffix...)
164
+
}
+678
confyg/read.go
+678
confyg/read.go
···
···
1
+
// Copyright 2018 The Go Authors. All rights reserved.
2
+
// Use of this source code is governed by a BSD-style
3
+
// license that can be found in the LICENSE file.
4
+
5
+
// Module file parser.
6
+
// This is a simplified copy of Google's buildifier parser.
7
+
8
+
package confyg
9
+
10
+
import (
11
+
"bytes"
12
+
"fmt"
13
+
"os"
14
+
"strings"
15
+
"unicode"
16
+
"unicode/utf8"
17
+
)
18
+
19
+
// A Position describes the position between two bytes of input.
20
+
type Position struct {
21
+
Line int // line in input (starting at 1)
22
+
LineRune int // rune in line (starting at 1)
23
+
Byte int // byte in input (starting at 0)
24
+
}
25
+
26
+
// add returns the position at the end of s, assuming it starts at p.
27
+
func (p Position) add(s string) Position {
28
+
p.Byte += len(s)
29
+
if n := strings.Count(s, "\n"); n > 0 {
30
+
p.Line += n
31
+
s = s[strings.LastIndex(s, "\n")+1:]
32
+
p.LineRune = 1
33
+
}
34
+
p.LineRune += utf8.RuneCountInString(s)
35
+
return p
36
+
}
37
+
38
+
// An Expr represents an input element.
39
+
type Expr interface {
40
+
// Span returns the start and end position of the expression,
41
+
// excluding leading or trailing comments.
42
+
Span() (start, end Position)
43
+
44
+
// Comment returns the comments attached to the expression.
45
+
// This method would normally be named 'Comments' but that
46
+
// would interfere with embedding a type of the same name.
47
+
Comment() *Comments
48
+
}
49
+
50
+
// A Comment represents a single // comment.
51
+
type Comment struct {
52
+
Start Position
53
+
Token string // without trailing newline
54
+
Suffix bool // an end of line (not whole line) comment
55
+
}
56
+
57
+
// Comments collects the comments associated with an expression.
58
+
type Comments struct {
59
+
Before []Comment // whole-line comments before this expression
60
+
Suffix []Comment // end-of-line comments after this expression
61
+
62
+
// For top-level expressions only, After lists whole-line
63
+
// comments following the expression.
64
+
After []Comment
65
+
}
66
+
67
+
// Comment returns the receiver. This isn't useful by itself, but
68
+
// a Comments struct is embedded into all the expression
69
+
// implementation types, and this gives each of those a Comment
70
+
// method to satisfy the Expr interface.
71
+
func (c *Comments) Comment() *Comments {
72
+
return c
73
+
}
74
+
75
+
// A FileSyntax represents an entire go.mod file.
76
+
type FileSyntax struct {
77
+
Name string // file path
78
+
Comments
79
+
Stmt []Expr
80
+
}
81
+
82
+
func (x *FileSyntax) Span() (start, end Position) {
83
+
if len(x.Stmt) == 0 {
84
+
return
85
+
}
86
+
start, _ = x.Stmt[0].Span()
87
+
_, end = x.Stmt[len(x.Stmt)-1].Span()
88
+
return start, end
89
+
}
90
+
91
+
// A CommentBlock represents a top-level block of comments separate
92
+
// from any rule.
93
+
type CommentBlock struct {
94
+
Comments
95
+
Start Position
96
+
}
97
+
98
+
func (x *CommentBlock) Span() (start, end Position) {
99
+
return x.Start, x.Start
100
+
}
101
+
102
+
// A Line is a single line of tokens.
103
+
type Line struct {
104
+
Comments
105
+
Start Position
106
+
Token []string
107
+
End Position
108
+
}
109
+
110
+
func (x *Line) Span() (start, end Position) {
111
+
return x.Start, x.End
112
+
}
113
+
114
+
// A LineBlock is a factored block of lines, like
115
+
//
116
+
// require (
117
+
// "x"
118
+
// "y"
119
+
// )
120
+
type LineBlock struct {
121
+
Comments
122
+
Start Position
123
+
LParen LParen
124
+
Token []string
125
+
Line []*Line
126
+
RParen RParen
127
+
}
128
+
129
+
func (x *LineBlock) Span() (start, end Position) {
130
+
return x.Start, x.RParen.Pos.add(")")
131
+
}
132
+
133
+
// An LParen represents the beginning of a parenthesized line block.
134
+
// It is a place to store suffix comments.
135
+
type LParen struct {
136
+
Comments
137
+
Pos Position
138
+
}
139
+
140
+
func (x *LParen) Span() (start, end Position) {
141
+
return x.Pos, x.Pos.add(")")
142
+
}
143
+
144
+
// An RParen represents the end of a parenthesized line block.
145
+
// It is a place to store whole-line (before) comments.
146
+
type RParen struct {
147
+
Comments
148
+
Pos Position
149
+
}
150
+
151
+
func (x *RParen) Span() (start, end Position) {
152
+
return x.Pos, x.Pos.add(")")
153
+
}
154
+
155
+
// An input represents a single input file being parsed.
156
+
type input struct {
157
+
// Lexing state.
158
+
filename string // name of input file, for errors
159
+
complete []byte // entire input
160
+
remaining []byte // remaining input
161
+
token []byte // token being scanned
162
+
lastToken string // most recently returned token, for error messages
163
+
pos Position // current input position
164
+
comments []Comment // accumulated comments
165
+
166
+
// Parser state.
167
+
file *FileSyntax // returned top-level syntax tree
168
+
parseError error // error encountered during parsing
169
+
170
+
// Comment assignment state.
171
+
pre []Expr // all expressions, in preorder traversal
172
+
post []Expr // all expressions, in postorder traversal
173
+
}
174
+
175
+
func newInput(filename string, data []byte) *input {
176
+
return &input{
177
+
filename: filename,
178
+
complete: data,
179
+
remaining: data,
180
+
pos: Position{Line: 1, LineRune: 1, Byte: 0},
181
+
}
182
+
}
183
+
184
+
// parse parses the input file.
185
+
func parse(file string, data []byte) (f *FileSyntax, err error) {
186
+
in := newInput(file, data)
187
+
// The parser panics for both routine errors like syntax errors
188
+
// and for programmer bugs like array index errors.
189
+
// Turn both into error returns. Catching bug panics is
190
+
// especially important when processing many files.
191
+
defer func() {
192
+
if e := recover(); e != nil {
193
+
if e == in.parseError {
194
+
err = in.parseError
195
+
} else {
196
+
err = fmt.Errorf("%s:%d:%d: internal error: %v", in.filename, in.pos.Line, in.pos.LineRune, e)
197
+
}
198
+
}
199
+
}()
200
+
201
+
// Invoke the parser.
202
+
in.parseFile()
203
+
if in.parseError != nil {
204
+
return nil, in.parseError
205
+
}
206
+
in.file.Name = in.filename
207
+
208
+
// Assign comments to nearby syntax.
209
+
in.assignComments()
210
+
211
+
return in.file, nil
212
+
}
213
+
214
+
// Error is called to report an error.
215
+
// The reason s is often "syntax error".
216
+
// Error does not return: it panics.
217
+
func (in *input) Error(s string) {
218
+
if s == "syntax error" && in.lastToken != "" {
219
+
s += " near " + in.lastToken
220
+
}
221
+
in.parseError = fmt.Errorf("%s:%d:%d: %v", in.filename, in.pos.Line, in.pos.LineRune, s)
222
+
panic(in.parseError)
223
+
}
224
+
225
+
// eof reports whether the input has reached end of file.
226
+
func (in *input) eof() bool {
227
+
return len(in.remaining) == 0
228
+
}
229
+
230
+
// peekRune returns the next rune in the input without consuming it.
231
+
func (in *input) peekRune() int {
232
+
if len(in.remaining) == 0 {
233
+
return 0
234
+
}
235
+
r, _ := utf8.DecodeRune(in.remaining)
236
+
return int(r)
237
+
}
238
+
239
+
// readRune consumes and returns the next rune in the input.
240
+
func (in *input) readRune() int {
241
+
if len(in.remaining) == 0 {
242
+
in.Error("internal lexer error: readRune at EOF")
243
+
}
244
+
r, size := utf8.DecodeRune(in.remaining)
245
+
in.remaining = in.remaining[size:]
246
+
if r == '\n' {
247
+
in.pos.Line++
248
+
in.pos.LineRune = 1
249
+
} else {
250
+
in.pos.LineRune++
251
+
}
252
+
in.pos.Byte += size
253
+
return int(r)
254
+
}
255
+
256
+
type symType struct {
257
+
pos Position
258
+
endPos Position
259
+
text string
260
+
}
261
+
262
+
// startToken marks the beginning of the next input token.
263
+
// It must be followed by a call to endToken, once the token has
264
+
// been consumed using readRune.
265
+
func (in *input) startToken(sym *symType) {
266
+
in.token = in.remaining
267
+
sym.text = ""
268
+
sym.pos = in.pos
269
+
}
270
+
271
+
// endToken marks the end of an input token.
272
+
// It records the actual token string in sym.text if the caller
273
+
// has not done that already.
274
+
func (in *input) endToken(sym *symType) {
275
+
if sym.text == "" {
276
+
tok := string(in.token[:len(in.token)-len(in.remaining)])
277
+
sym.text = tok
278
+
in.lastToken = sym.text
279
+
}
280
+
sym.endPos = in.pos
281
+
}
282
+
283
+
// lex is called from the parser to obtain the next input token.
284
+
// It returns the token value (either a rune like '+' or a symbolic token _FOR)
285
+
// and sets val to the data associated with the token.
286
+
// For all our input tokens, the associated data is
287
+
// val.Pos (the position where the token begins)
288
+
// and val.Token (the input string corresponding to the token).
289
+
func (in *input) lex(sym *symType) int {
290
+
// Skip past spaces, stopping at non-space or EOF.
291
+
countNL := 0 // number of newlines we've skipped past
292
+
for !in.eof() {
293
+
// Skip over spaces. Count newlines so we can give the parser
294
+
// information about where top-level blank lines are,
295
+
// for top-level comment assignment.
296
+
c := in.peekRune()
297
+
if c == ' ' || c == '\t' || c == '\r' {
298
+
in.readRune()
299
+
continue
300
+
}
301
+
302
+
// Comment runs to end of line.
303
+
if c == '#' {
304
+
in.startToken(sym)
305
+
306
+
// Is this comment the only thing on its line?
307
+
// Find the last \n before this // and see if it's all
308
+
// spaces from there to here.
309
+
i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
310
+
suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
311
+
in.readRune()
312
+
c = in.peekRune()
313
+
if c != '#' {
314
+
in.Error(fmt.Sprintf("unexpected input character %#q", c))
315
+
}
316
+
317
+
// Consume comment.
318
+
for len(in.remaining) > 0 && in.readRune() != '\n' {
319
+
}
320
+
in.endToken(sym)
321
+
322
+
sym.text = strings.TrimRight(sym.text, "\n")
323
+
in.lastToken = "comment"
324
+
325
+
// If we are at top level (not in a statement), hand the comment to
326
+
// the parser as a _COMMENT token. The grammar is written
327
+
// to handle top-level comments itself.
328
+
if !suffix {
329
+
// Not in a statement. Tell parser about top-level comment.
330
+
return _COMMENT
331
+
}
332
+
333
+
// Otherwise, save comment for later attachment to syntax tree.
334
+
if countNL > 1 {
335
+
in.comments = append(in.comments, Comment{sym.pos, "", false})
336
+
}
337
+
in.comments = append(in.comments, Comment{sym.pos, sym.text, suffix})
338
+
countNL = 1
339
+
return _EOL
340
+
}
341
+
342
+
// Found non-space non-comment.
343
+
break
344
+
}
345
+
346
+
// Found the beginning of the next token.
347
+
in.startToken(sym)
348
+
defer in.endToken(sym)
349
+
350
+
// End of file.
351
+
if in.eof() {
352
+
in.lastToken = "EOF"
353
+
return _EOF
354
+
}
355
+
356
+
// Punctuation tokens.
357
+
switch c := in.peekRune(); c {
358
+
case '\n':
359
+
in.readRune()
360
+
return c
361
+
362
+
case '(':
363
+
in.readRune()
364
+
return c
365
+
366
+
case ')':
367
+
in.readRune()
368
+
return c
369
+
370
+
case '"', '`': // quoted string
371
+
quote := c
372
+
in.readRune()
373
+
for {
374
+
if in.eof() {
375
+
in.pos = sym.pos
376
+
in.Error("unexpected EOF in string")
377
+
}
378
+
if in.peekRune() == '\n' {
379
+
in.Error("unexpected newline in string")
380
+
}
381
+
c := in.readRune()
382
+
if c == quote {
383
+
break
384
+
}
385
+
if c == '\\' && quote != '`' {
386
+
if in.eof() {
387
+
in.pos = sym.pos
388
+
in.Error("unexpected EOF in string")
389
+
}
390
+
in.readRune()
391
+
}
392
+
}
393
+
in.endToken(sym)
394
+
return _STRING
395
+
}
396
+
397
+
// Checked all punctuation. Must be identifier token.
398
+
if c := in.peekRune(); !isIdent(c) {
399
+
in.Error(fmt.Sprintf("unexpected input character %#q", c))
400
+
}
401
+
402
+
// Scan over identifier.
403
+
for isIdent(in.peekRune()) {
404
+
in.readRune()
405
+
}
406
+
return _IDENT
407
+
}
408
+
409
+
// isIdent reports whether c is an identifier rune.
410
+
// We treat nearly all runes as identifier runes.
411
+
func isIdent(c int) bool {
412
+
return c != 0 && !unicode.IsSpace(rune(c)) && c != '(' && c != ')' && c != '"' && c != '`'
413
+
}
414
+
415
+
// Comment assignment.
416
+
// We build two lists of all subexpressions, preorder and postorder.
417
+
// The preorder list is ordered by start location, with outer expressions first.
418
+
// The postorder list is ordered by end location, with outer expressions last.
419
+
// We use the preorder list to assign each whole-line comment to the syntax
420
+
// immediately following it, and we use the postorder list to assign each
421
+
// end-of-line comment to the syntax immediately preceding it.
422
+
423
+
// order walks the expression adding it and its subexpressions to the
424
+
// preorder and postorder lists.
425
+
func (in *input) order(x Expr) {
426
+
if x != nil {
427
+
in.pre = append(in.pre, x)
428
+
}
429
+
switch x := x.(type) {
430
+
default:
431
+
panic(fmt.Errorf("order: unexpected type %T", x))
432
+
case nil:
433
+
// nothing
434
+
case *LParen, *RParen:
435
+
// nothing
436
+
case *CommentBlock:
437
+
// nothing
438
+
case *Line:
439
+
// nothing
440
+
case *FileSyntax:
441
+
for _, stmt := range x.Stmt {
442
+
in.order(stmt)
443
+
}
444
+
case *LineBlock:
445
+
in.order(&x.LParen)
446
+
for _, l := range x.Line {
447
+
in.order(l)
448
+
}
449
+
in.order(&x.RParen)
450
+
}
451
+
if x != nil {
452
+
in.post = append(in.post, x)
453
+
}
454
+
}
455
+
456
+
// assignComments attaches comments to nearby syntax.
457
+
func (in *input) assignComments() {
458
+
const debug = false
459
+
460
+
// Generate preorder and postorder lists.
461
+
in.order(in.file)
462
+
463
+
// Split into whole-line comments and suffix comments.
464
+
var line, suffix []Comment
465
+
for _, com := range in.comments {
466
+
if com.Suffix {
467
+
suffix = append(suffix, com)
468
+
} else {
469
+
line = append(line, com)
470
+
}
471
+
}
472
+
473
+
if debug {
474
+
for _, c := range line {
475
+
fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
476
+
}
477
+
}
478
+
479
+
// Assign line comments to syntax immediately following.
480
+
for _, x := range in.pre {
481
+
start, _ := x.Span()
482
+
if debug {
483
+
fmt.Printf("pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
484
+
}
485
+
xcom := x.Comment()
486
+
for len(line) > 0 && start.Byte >= line[0].Start.Byte {
487
+
if debug {
488
+
fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
489
+
}
490
+
xcom.Before = append(xcom.Before, line[0])
491
+
line = line[1:]
492
+
}
493
+
}
494
+
495
+
// Remaining line comments go at end of file.
496
+
in.file.After = append(in.file.After, line...)
497
+
498
+
if debug {
499
+
for _, c := range suffix {
500
+
fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
501
+
}
502
+
}
503
+
504
+
// Assign suffix comments to syntax immediately before.
505
+
for i := len(in.post) - 1; i >= 0; i-- {
506
+
x := in.post[i]
507
+
508
+
start, end := x.Span()
509
+
if debug {
510
+
fmt.Printf("post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
511
+
}
512
+
513
+
// Do not assign suffix comments to end of line block or whole file.
514
+
// Instead assign them to the last element inside.
515
+
switch x.(type) {
516
+
case *FileSyntax:
517
+
continue
518
+
}
519
+
520
+
// Do not assign suffix comments to something that starts
521
+
// on an earlier line, so that in
522
+
//
523
+
// x ( y
524
+
// z ) // comment
525
+
//
526
+
// we assign the comment to z and not to x ( ... ).
527
+
if start.Line != end.Line {
528
+
continue
529
+
}
530
+
xcom := x.Comment()
531
+
for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
532
+
if debug {
533
+
fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
534
+
}
535
+
xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
536
+
suffix = suffix[:len(suffix)-1]
537
+
}
538
+
}
539
+
540
+
// We assigned suffix comments in reverse.
541
+
// If multiple suffix comments were appended to the same
542
+
// expression node, they are now in reverse. Fix that.
543
+
for _, x := range in.post {
544
+
reverseComments(x.Comment().Suffix)
545
+
}
546
+
547
+
// Remaining suffix comments go at beginning of file.
548
+
in.file.Before = append(in.file.Before, suffix...)
549
+
}
550
+
551
+
// reverseComments reverses the []Comment list.
552
+
func reverseComments(list []Comment) {
553
+
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
554
+
list[i], list[j] = list[j], list[i]
555
+
}
556
+
}
557
+
558
+
func (in *input) parseFile() {
559
+
in.file = new(FileSyntax)
560
+
var sym symType
561
+
var cb *CommentBlock
562
+
for {
563
+
tok := in.lex(&sym)
564
+
switch tok {
565
+
case '\n':
566
+
if cb != nil {
567
+
in.file.Stmt = append(in.file.Stmt, cb)
568
+
cb = nil
569
+
}
570
+
case _COMMENT:
571
+
if cb == nil {
572
+
cb = &CommentBlock{Start: sym.pos}
573
+
}
574
+
com := cb.Comment()
575
+
com.Before = append(com.Before, Comment{Start: sym.pos, Token: sym.text})
576
+
case _EOF:
577
+
if cb != nil {
578
+
in.file.Stmt = append(in.file.Stmt, cb)
579
+
}
580
+
return
581
+
default:
582
+
in.parseStmt(&sym)
583
+
if cb != nil {
584
+
in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
585
+
cb = nil
586
+
}
587
+
}
588
+
}
589
+
}
590
+
591
+
func (in *input) parseStmt(sym *symType) {
592
+
start := sym.pos
593
+
end := sym.endPos
594
+
token := []string{sym.text}
595
+
for {
596
+
tok := in.lex(sym)
597
+
switch tok {
598
+
case '\n', _EOF, _EOL:
599
+
in.file.Stmt = append(in.file.Stmt, &Line{
600
+
Start: start,
601
+
Token: token,
602
+
End: end,
603
+
})
604
+
return
605
+
case '(':
606
+
in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, token, sym))
607
+
return
608
+
default:
609
+
token = append(token, sym.text)
610
+
end = sym.endPos
611
+
}
612
+
}
613
+
}
614
+
615
+
func (in *input) parseLineBlock(start Position, token []string, sym *symType) *LineBlock {
616
+
x := &LineBlock{
617
+
Start: start,
618
+
Token: token,
619
+
LParen: LParen{Pos: sym.pos},
620
+
}
621
+
var comments []Comment
622
+
for {
623
+
tok := in.lex(sym)
624
+
switch tok {
625
+
case _EOL:
626
+
// ignore
627
+
case '\n':
628
+
if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
629
+
comments = append(comments, Comment{})
630
+
}
631
+
case _COMMENT:
632
+
comments = append(comments, Comment{Start: sym.pos, Token: sym.text})
633
+
case _EOF:
634
+
in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
635
+
case ')':
636
+
x.RParen.Before = comments
637
+
x.RParen.Pos = sym.pos
638
+
tok = in.lex(sym)
639
+
if tok != '\n' && tok != _EOF && tok != _EOL {
640
+
in.Error("syntax error (expected newline after closing paren)")
641
+
}
642
+
return x
643
+
default:
644
+
l := in.parseLine(sym)
645
+
x.Line = append(x.Line, l)
646
+
l.Comment().Before = comments
647
+
comments = nil
648
+
}
649
+
}
650
+
}
651
+
652
+
func (in *input) parseLine(sym *symType) *Line {
653
+
start := sym.pos
654
+
end := sym.endPos
655
+
token := []string{sym.text}
656
+
for {
657
+
tok := in.lex(sym)
658
+
switch tok {
659
+
case '\n', _EOF, _EOL:
660
+
return &Line{
661
+
Start: start,
662
+
Token: token,
663
+
End: end,
664
+
}
665
+
default:
666
+
token = append(token, sym.text)
667
+
end = sym.endPos
668
+
}
669
+
}
670
+
}
671
+
672
+
const (
673
+
_EOF = -(1 + iota)
674
+
_EOL
675
+
_IDENT
676
+
_STRING
677
+
_COMMENT
678
+
)
+95
confyg/read_test.go
+95
confyg/read_test.go
···
···
1
+
// Copyright 2018 The Go Authors. All rights reserved.
2
+
// Use of this source code is governed by a BSD-style
3
+
// license that can be found in the LICENSE file.
4
+
5
+
package confyg
6
+
7
+
import (
8
+
"bytes"
9
+
"os"
10
+
"os/exec"
11
+
"path/filepath"
12
+
"testing"
13
+
)
14
+
15
+
// Test that reading and then writing the golden files
16
+
// does not change their output.
17
+
func TestPrintGolden(t *testing.T) {
18
+
outs, err := filepath.Glob("testdata/*.golden")
19
+
if err != nil {
20
+
t.Fatal(err)
21
+
}
22
+
for _, out := range outs {
23
+
testPrint(t, out, out)
24
+
}
25
+
}
26
+
27
+
// testPrint is a helper for testing the printer.
28
+
// It reads the file named in, reformats it, and compares
29
+
// the result to the file named out.
30
+
func testPrint(t *testing.T, in, out string) {
31
+
data, err := os.ReadFile(in)
32
+
if err != nil {
33
+
t.Error(err)
34
+
return
35
+
}
36
+
37
+
golden, err := os.ReadFile(out)
38
+
if err != nil {
39
+
t.Error(err)
40
+
return
41
+
}
42
+
43
+
base := "testdata/" + filepath.Base(in)
44
+
f, err := parse(in, data)
45
+
if err != nil {
46
+
t.Error(err)
47
+
return
48
+
}
49
+
50
+
ndata := Format(f)
51
+
52
+
if !bytes.Equal(ndata, golden) {
53
+
t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base)
54
+
tdiff(t, string(golden), string(ndata))
55
+
return
56
+
}
57
+
}
58
+
59
+
// diff returns the output of running diff on b1 and b2.
60
+
func diff(b1, b2 []byte) (data []byte, err error) {
61
+
f1, err := os.CreateTemp("", "testdiff")
62
+
if err != nil {
63
+
return nil, err
64
+
}
65
+
defer os.Remove(f1.Name())
66
+
defer f1.Close()
67
+
68
+
f2, err := os.CreateTemp("", "testdiff")
69
+
if err != nil {
70
+
return nil, err
71
+
}
72
+
defer os.Remove(f2.Name())
73
+
defer f2.Close()
74
+
75
+
f1.Write(b1)
76
+
f2.Write(b2)
77
+
78
+
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
79
+
if len(data) > 0 {
80
+
// diff exits with a non-zero status when the files don't match.
81
+
// Ignore that failure as long as we get output.
82
+
err = nil
83
+
}
84
+
return
85
+
}
86
+
87
+
// tdiff logs the diff output to t.Error.
88
+
func tdiff(t *testing.T, a, b string) {
89
+
data, err := diff([]byte(a), []byte(b))
90
+
if err != nil {
91
+
t.Error(err)
92
+
return
93
+
}
94
+
t.Error(string(data))
95
+
}
+19
confyg/reader.go
+19
confyg/reader.go
···
···
1
+
package confyg
2
+
3
+
import "bytes"
4
+
5
+
// Reader is called when individual lines of the configuration file are being read.
6
+
// This is where you should populate any relevant structures with information.
7
+
//
8
+
// If something goes wrong in the file parsing step, add data to the errs buffer
9
+
// describing what went wrong.
10
+
type Reader interface {
11
+
Read(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string)
12
+
}
13
+
14
+
// ReaderFunc implements Reader for inline definitions.
15
+
type ReaderFunc func(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string)
16
+
17
+
func (r ReaderFunc) Read(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string) {
18
+
r(errs, fs, line, verb, args)
19
+
}
+59
confyg/reader_test.go
+59
confyg/reader_test.go
···
···
1
+
package confyg
2
+
3
+
import (
4
+
"bytes"
5
+
"fmt"
6
+
"testing"
7
+
)
8
+
9
+
func TestReader(t *testing.T) {
10
+
done := false
11
+
acc := 0
12
+
13
+
al := AllowerFunc(func(verb string, block bool) bool {
14
+
switch verb {
15
+
case "test":
16
+
return !block
17
+
18
+
case "acc":
19
+
return true
20
+
default:
21
+
return false
22
+
}
23
+
})
24
+
25
+
r := ReaderFunc(func(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string) {
26
+
switch verb {
27
+
case "test":
28
+
done = len(args) == 1
29
+
case "acc":
30
+
acc++
31
+
default:
32
+
fmt.Fprintf(errs, "%s:%d unknown verb %s\n", fs.Name, line.Start.Line, verb)
33
+
}
34
+
})
35
+
const configFile = `test "42"
36
+
37
+
acc (
38
+
1
39
+
2
40
+
3
41
+
)`
42
+
43
+
fs, err := Parse("test.cfg", []byte(configFile), r, al)
44
+
if err != nil {
45
+
t.Fatal(err)
46
+
}
47
+
48
+
_ = fs
49
+
50
+
t.Logf("done: %v", done)
51
+
if !done {
52
+
t.Fatal("done was not flagged")
53
+
}
54
+
55
+
t.Logf("acc: %v", acc)
56
+
if acc != 3 {
57
+
t.Fatal("acc was not changed")
58
+
}
59
+
}
+53
confyg/rule.go
+53
confyg/rule.go
···
···
1
+
// Copyright 2018 The Go Authors. All rights reserved.
2
+
// Use of this source code is governed by a BSD-style
3
+
// license that can be found in the LICENSE file.
4
+
5
+
package confyg
6
+
7
+
import (
8
+
"bytes"
9
+
"errors"
10
+
"fmt"
11
+
"strings"
12
+
)
13
+
14
+
func Parse(file string, data []byte, r Reader, al Allower) (*FileSyntax, error) {
15
+
fs, err := parse(file, data)
16
+
if err != nil {
17
+
return nil, err
18
+
}
19
+
20
+
var errs bytes.Buffer
21
+
for _, x := range fs.Stmt {
22
+
switch x := x.(type) {
23
+
case *Line:
24
+
ok := al.Allow(x.Token[0], false)
25
+
if ok {
26
+
r.Read(&errs, fs, x, x.Token[0], x.Token[1:])
27
+
continue
28
+
}
29
+
30
+
fmt.Fprintf(&errs, "%s:%d: can't allow line verb %s", file, x.Start.Line, x.Token[0])
31
+
32
+
case *LineBlock:
33
+
if len(x.Token) > 1 {
34
+
fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
35
+
continue
36
+
}
37
+
ok := al.Allow(x.Token[0], true)
38
+
if ok {
39
+
for _, l := range x.Line {
40
+
r.Read(&errs, fs, l, x.Token[0], l.Token)
41
+
}
42
+
continue
43
+
}
44
+
45
+
fmt.Fprintf(&errs, "%s:%d: can't allow line block verb %s", file, x.Start.Line, x.Token[0])
46
+
}
47
+
}
48
+
49
+
if errs.Len() > 0 {
50
+
return nil, errors.New(strings.TrimRight(errs.String(), "\n"))
51
+
}
52
+
return fs, nil
53
+
}
+29
confyg/testdata/block.golden
+29
confyg/testdata/block.golden
···
···
1
+
## comment
2
+
x "y" z
3
+
4
+
## block
5
+
block ( ## block-eol
6
+
## x-before-line
7
+
8
+
"x" ( y ## x-eol
9
+
"x1"
10
+
"x2"
11
+
## line
12
+
"x3"
13
+
"x4"
14
+
15
+
"x5"
16
+
17
+
## y-line
18
+
"y" ## y-eol
19
+
20
+
"z" ## z-eol
21
+
) ## block-eol2
22
+
23
+
block2 (
24
+
x
25
+
y
26
+
z
27
+
)
28
+
29
+
## eof
+29
confyg/testdata/block.in
+29
confyg/testdata/block.in
···
···
1
+
## comment
2
+
x "y" z
3
+
4
+
## block
5
+
block ( ## block-eol
6
+
## x-before-line
7
+
8
+
"x" ( y ## x-eol
9
+
"x1"
10
+
"x2"
11
+
## line
12
+
"x3"
13
+
"x4"
14
+
15
+
"x5"
16
+
17
+
## y-line
18
+
"y" ## y-eol
19
+
20
+
"z" ## z-eol
21
+
) ## block-eol2
22
+
23
+
24
+
block2 (x
25
+
y
26
+
z
27
+
)
28
+
29
+
## eof
+10
confyg/testdata/comment.golden
+10
confyg/testdata/comment.golden
+8
confyg/testdata/comment.in
+8
confyg/testdata/comment.in
confyg/testdata/empty.golden
confyg/testdata/empty.golden
This is a binary file and will not be displayed.
confyg/testdata/empty.in
confyg/testdata/empty.in
This is a binary file and will not be displayed.
+1
confyg/testdata/module.golden
+1
confyg/testdata/module.golden
···
···
1
+
module "abc"
+1
confyg/testdata/module.in
+1
confyg/testdata/module.in
···
···
1
+
module "abc"
+5
confyg/testdata/replace.golden
+5
confyg/testdata/replace.golden
+5
confyg/testdata/replace.in
+5
confyg/testdata/replace.in
+6
confyg/testdata/replace2.golden
+6
confyg/testdata/replace2.golden
+6
confyg/testdata/replace2.in
+6
confyg/testdata/replace2.in
+7
confyg/testdata/rule1.golden
+7
confyg/testdata/rule1.golden
+1
confyg/testdata/url.golden
+1
confyg/testdata/url.golden
···
···
1
+
url https://foo.bar
+6
-4
go.mod
+6
-4
go.mod
···
8
github.com/Songmu/gitconfig v0.2.0
9
github.com/cavaliergopher/rpm v1.3.0
10
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c
11
-
github.com/goreleaser/nfpm/v2 v2.42.0
12
github.com/pkg/errors v0.9.1
13
pault.ag/go/debian v0.18.0
14
)
15
16
require (
17
-
dario.cat/mergo v1.0.1 // indirect
18
github.com/AlekSi/pointer v1.2.0 // indirect
19
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
20
github.com/Masterminds/goutils v1.1.1 // indirect
21
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
22
github.com/Microsoft/go-winio v0.6.2 // indirect
23
-
github.com/ProtonMail/go-crypto v1.1.6 // indirect
24
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
25
github.com/cavaliergopher/cpio v1.0.1 // indirect
26
github.com/cli/go-gh v0.1.0 // indirect
···
39
github.com/goccy/go-yaml v1.12.0 // indirect
40
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
41
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
42
-
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
43
github.com/google/uuid v1.6.0 // indirect
44
github.com/goreleaser/chglog v0.7.0 // indirect
45
github.com/goreleaser/fileglob v1.3.0 // indirect
···
70
golang.org/x/net v0.39.0 // indirect
71
golang.org/x/sync v0.13.0 // indirect
72
golang.org/x/sys v0.32.0 // indirect
73
golang.org/x/text v0.24.0 // indirect
74
golang.org/x/tools v0.32.0 // indirect
75
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
···
8
github.com/Songmu/gitconfig v0.2.0
9
github.com/cavaliergopher/rpm v1.3.0
10
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c
11
+
github.com/goreleaser/nfpm/v2 v2.42.1
12
github.com/pkg/errors v0.9.1
13
+
mvdan.cc/sh/v3 v3.11.0
14
pault.ag/go/debian v0.18.0
15
)
16
17
require (
18
+
dario.cat/mergo v1.0.2 // indirect
19
github.com/AlekSi/pointer v1.2.0 // indirect
20
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
21
github.com/Masterminds/goutils v1.1.1 // indirect
22
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
23
github.com/Microsoft/go-winio v0.6.2 // indirect
24
+
github.com/ProtonMail/go-crypto v1.2.0 // indirect
25
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
26
github.com/cavaliergopher/cpio v1.0.1 // indirect
27
github.com/cli/go-gh v0.1.0 // indirect
···
40
github.com/goccy/go-yaml v1.12.0 // indirect
41
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
42
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
43
+
github.com/google/rpmpack v0.6.1-0.20250405124433-758cc6896cbc // indirect
44
github.com/google/uuid v1.6.0 // indirect
45
github.com/goreleaser/chglog v0.7.0 // indirect
46
github.com/goreleaser/fileglob v1.3.0 // indirect
···
71
golang.org/x/net v0.39.0 // indirect
72
golang.org/x/sync v0.13.0 // indirect
73
golang.org/x/sys v0.32.0 // indirect
74
+
golang.org/x/term v0.31.0 // indirect
75
golang.org/x/text v0.24.0 // indirect
76
golang.org/x/tools v0.32.0 // indirect
77
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
+14
-8
go.sum
+14
-8
go.sum
···
1
al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
2
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
3
-
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
4
-
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
5
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
6
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
7
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
···
18
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
19
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
20
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
21
-
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
22
-
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
23
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
24
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
25
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
···
46
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
47
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
48
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
49
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
50
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
51
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
···
86
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
87
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
88
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
89
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
90
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
91
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
···
99
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
100
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
101
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
102
-
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
103
-
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
104
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
105
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
106
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
···
111
github.com/goreleaser/chglog v0.7.0/go.mod h1:2h/yyq9xvTUeM9tOoucBP+jri8Dj28splx+SjlYkklc=
112
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
113
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
114
-
github.com/goreleaser/nfpm/v2 v2.42.0 h1:7BW4WQWyvZDrT0C7SyWop+J8rtqFyTB17Sb2/j/NxMI=
115
-
github.com/goreleaser/nfpm/v2 v2.42.0/go.mod h1:DtNL+nKpfB8sMFZp+X7Xu3W64atyZYtTnYe8O925/mg=
116
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
117
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
118
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
···
276
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
277
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
278
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
279
pault.ag/go/debian v0.18.0 h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ=
280
pault.ag/go/debian v0.18.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE=
281
pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
···
1
al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
2
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
3
+
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
4
+
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
5
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
6
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
7
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
···
18
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
19
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
20
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
21
+
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
22
+
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
23
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
24
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
25
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
···
46
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
47
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
48
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
49
+
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
50
+
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
51
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
52
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
53
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
···
88
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
89
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
90
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
91
+
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
92
+
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
93
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
94
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
95
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
···
103
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
104
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
105
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
106
+
github.com/google/rpmpack v0.6.1-0.20250405124433-758cc6896cbc h1:qES+d3PvR9CN+zARQQH/bNXH0ybzmdjNMHICrBwXD28=
107
+
github.com/google/rpmpack v0.6.1-0.20250405124433-758cc6896cbc/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
108
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
109
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
110
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
···
115
github.com/goreleaser/chglog v0.7.0/go.mod h1:2h/yyq9xvTUeM9tOoucBP+jri8Dj28splx+SjlYkklc=
116
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
117
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
118
+
github.com/goreleaser/nfpm/v2 v2.42.1 h1:xu2pLRgQuz2ab+YZFoeIzwU/M5jjjCKDGwv1lRbVGvk=
119
+
github.com/goreleaser/nfpm/v2 v2.42.1/go.mod h1:dY53KWYKebkOocxgkmpM7SRX0Nv5hU+jEu2kIaM4/LI=
120
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
121
github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo=
122
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
···
280
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
281
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
282
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
283
+
mvdan.cc/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
284
+
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
285
pault.ag/go/debian v0.18.0 h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ=
286
pault.ag/go/debian v0.18.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE=
287
pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
-27
internal/confyg/LICENSE
-27
internal/confyg/LICENSE
···
1
-
Copyright (c) 2009 The Go Authors. All rights reserved.
2
-
3
-
Redistribution and use in source and binary forms, with or without
4
-
modification, are permitted provided that the following conditions are
5
-
met:
6
-
7
-
* Redistributions of source code must retain the above copyright
8
-
notice, this list of conditions and the following disclaimer.
9
-
* Redistributions in binary form must reproduce the above
10
-
copyright notice, this list of conditions and the following disclaimer
11
-
in the documentation and/or other materials provided with the
12
-
distribution.
13
-
* Neither the name of Google Inc. nor the names of its
14
-
contributors may be used to endorse or promote products derived from
15
-
this software without specific prior written permission.
16
-
17
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
-
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
-
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
-
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
-
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
-
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
-
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
-
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
-
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
-
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
···
-109
internal/confyg/README.md
-109
internal/confyg/README.md
···
1
-
# confyg
2
-
3
-
A suitably generic form of the Go module configuration file parser.
4
-
5
-
[](https://godoc.org/within.website/confyg)
6
-
7
-
Usage is simple:
8
-
9
-
```go
10
-
type server struct {
11
-
port string
12
-
keys *crypto.Keypair
13
-
db *storm.DB
14
-
}
15
-
16
-
func (s *server) Allow(verb string, block bool) bool {
17
-
switch verb {
18
-
case "port":
19
-
return !block
20
-
case "dbfile":
21
-
return !block
22
-
case "keys":
23
-
return !block
24
-
}
25
-
26
-
return false
27
-
}
28
-
29
-
func (s *server) Read(errs *bytes.Buffer, fs *confyg.FileSyntax, line *confyg.Line, verb string, args []string) {
30
-
switch verb {
31
-
case "port":
32
-
_, err := strconv.Atoi(args[0])
33
-
if err != nil {
34
-
fmt.Fprintf(errs, "%s:%d value is not a number: %s: %v\n", fs.Name, line.Start.Line, args[0], err)
35
-
return
36
-
}
37
-
38
-
s.port = args[0]
39
-
40
-
case "dbfile":
41
-
dbFile := args[0][1 : len(args[0])-1] // shuck off quotes
42
-
43
-
db, err := storm.Open(dbFile)
44
-
if err != nil {
45
-
fmt.Fprintf(errs, "%s:%d failed to open storm database: %s: %v\n", fs.Name, line.Start.Line, args[0], err)
46
-
return
47
-
}
48
-
49
-
s.db = db
50
-
51
-
case "keys":
52
-
kp := &crypto.Keypair{}
53
-
54
-
pubk, err := hex.DecodeString(args[0])
55
-
if err != nil {
56
-
fmt.Fprintf(errs, "%s:%d invalid public key: %v\n", fs.Name, line.Start.Line, err)
57
-
return
58
-
}
59
-
60
-
privk, err := hex.DecodeString(args[1])
61
-
if err != nil {
62
-
fmt.Fprintf(errs, "%s:%d invalid private key: %v\n", fs.Name, line.Start.Line, err)
63
-
return
64
-
}
65
-
66
-
copy(kp.Public[:], pubk[0:32])
67
-
copy(kp.Private[:], privk[0:32])
68
-
69
-
s.keys = kp
70
-
}
71
-
}
72
-
73
-
var (
74
-
configFile = flag.String("cfg", "./apig.cfg", "apig config file location")
75
-
)
76
-
77
-
func main() {
78
-
flag.Parse()
79
-
80
-
data, err := ioutil.ReadFile(*configFile)
81
-
if err != nil {
82
-
log.Fatal(err)
83
-
}
84
-
85
-
s := &server{}
86
-
_, err = confyg.Parse(*configFile, data, s, s)
87
-
if err != nil {
88
-
log.Fatal(err)
89
-
}
90
-
91
-
_ = s
92
-
}
93
-
```
94
-
95
-
Or use [`flagconfyg`](https://godoc.org/within.website/confyg/flagconfyg):
96
-
97
-
```go
98
-
var (
99
-
config = flag.Config("cfg", "", "if set, configuration file to load (see https://github.com/Xe/x/blob/master/docs/man/flagconfyg.5)")
100
-
)
101
-
102
-
func main() {
103
-
flag.Parse()
104
-
105
-
if *config != "" {
106
-
flagconfyg.CmdParse(*config)
107
-
}
108
-
}
109
-
```
···
-19
internal/confyg/allower.go
-19
internal/confyg/allower.go
···
1
-
package confyg
2
-
3
-
// Allower defines if a given verb and block combination is valid for
4
-
// configuration parsing.
5
-
//
6
-
// If this is intended to be a statement-like verb, block should be set
7
-
// to false. If this is intended to be a block-like verb, block should
8
-
// be set to true.
9
-
type Allower interface {
10
-
Allow(verb string, block bool) bool
11
-
}
12
-
13
-
// AllowerFunc implements Allower for inline definitions.
14
-
type AllowerFunc func(verb string, block bool) bool
15
-
16
-
// Allow implements Allower.
17
-
func (a AllowerFunc) Allow(verb string, block bool) bool {
18
-
return a(verb, block)
19
-
}
···
-48
internal/confyg/allower_test.go
-48
internal/confyg/allower_test.go
···
1
-
package confyg
2
-
3
-
import (
4
-
"fmt"
5
-
"testing"
6
-
)
7
-
8
-
func TestAllower(t *testing.T) {
9
-
al := AllowerFunc(func(verb string, block bool) bool {
10
-
switch verb {
11
-
case "project":
12
-
if block {
13
-
return false
14
-
}
15
-
16
-
return true
17
-
}
18
-
19
-
return false
20
-
})
21
-
22
-
cases := []struct {
23
-
verb string
24
-
block bool
25
-
want bool
26
-
}{
27
-
{
28
-
verb: "project",
29
-
block: false,
30
-
want: true,
31
-
},
32
-
{
33
-
verb: "nonsense",
34
-
block: true,
35
-
want: false,
36
-
},
37
-
}
38
-
39
-
for _, cs := range cases {
40
-
t.Run(fmt.Sprint(cs), func(t *testing.T) {
41
-
result := al.Allow(cs.verb, cs.block)
42
-
43
-
if result != cs.want {
44
-
t.Fatalf("wanted Allow(%q, %v) == %v, got: %v", cs.verb, cs.block, cs.want, result)
45
-
}
46
-
})
47
-
}
48
-
}
···
-46
internal/confyg/flagconfyg/flagconfyg.go
-46
internal/confyg/flagconfyg/flagconfyg.go
···
1
-
// Package flagconfyg is a hack around confyg. This will blindly convert config
2
-
// verbs to flag values.
3
-
package flagconfyg
4
-
5
-
import (
6
-
"bytes"
7
-
"context"
8
-
"flag"
9
-
"log"
10
-
"os"
11
-
"strings"
12
-
13
-
"github.com/TecharoHQ/yeet/internal/confyg"
14
-
)
15
-
16
-
// CmdParse is a quick wrapper for command usage. It explodes on errors.
17
-
func CmdParse(ctx context.Context, path string) {
18
-
data, err := os.ReadFile(path)
19
-
if err != nil {
20
-
return
21
-
}
22
-
23
-
err = Parse(path, data, flag.CommandLine)
24
-
if err != nil {
25
-
log.Printf("can't parse %s: %v", path, err)
26
-
return
27
-
}
28
-
}
29
-
30
-
// Parse parses the config file in the given file by name, bytes data and into
31
-
// the given flagset.
32
-
func Parse(name string, data []byte, fs *flag.FlagSet) error {
33
-
lineRead := func(errs *bytes.Buffer, fs_ *confyg.FileSyntax, line *confyg.Line, verb string, args []string) {
34
-
err := fs.Set(verb, strings.Join(args, " "))
35
-
if err != nil {
36
-
errs.WriteString(err.Error())
37
-
}
38
-
}
39
-
40
-
_, err := confyg.Parse(name, data, confyg.ReaderFunc(lineRead), confyg.AllowerFunc(allower))
41
-
return err
42
-
}
43
-
44
-
func allower(verb string, block bool) bool {
45
-
return true
46
-
}
···
-31
internal/confyg/flagconfyg/flagconfyg_test.go
-31
internal/confyg/flagconfyg/flagconfyg_test.go
···
1
-
package flagconfyg
2
-
3
-
import (
4
-
"flag"
5
-
"testing"
6
-
)
7
-
8
-
func TestFlagConfyg(t *testing.T) {
9
-
fs := flag.NewFlagSet("test", flag.PanicOnError)
10
-
sc := fs.String("subscribe", "", "to pewdiepie")
11
-
us := fs.String("unsubscribe", "all the time", "from t-series")
12
-
13
-
const configFile = `subscribe pewdiepie
14
-
15
-
unsubscribe (
16
-
t-series
17
-
)`
18
-
19
-
err := Parse("test.cfg", []byte(configFile), fs)
20
-
if err != nil {
21
-
t.Fatal(err)
22
-
}
23
-
24
-
if *sc != "pewdiepie" {
25
-
t.Errorf("wanted subscribe->pewdiepie, got: %s", *sc)
26
-
}
27
-
28
-
if *us != "t-series" {
29
-
t.Errorf("wanted unsubscribe->t-series, got: %s", *us)
30
-
}
31
-
}
···
-18
internal/confyg/map_output.go
-18
internal/confyg/map_output.go
···
1
-
package confyg
2
-
3
-
import (
4
-
"bytes"
5
-
"strings"
6
-
)
7
-
8
-
// MapConfig is a simple wrapper around a map.
9
-
type MapConfig map[string][]string
10
-
11
-
// Allow accepts everything.
12
-
func (mc MapConfig) Allow(verb string, block bool) bool {
13
-
return true
14
-
}
15
-
16
-
func (mc MapConfig) Read(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string) {
17
-
mc[verb] = append(mc[verb], strings.Join(args, " "))
18
-
}
···
-26
internal/confyg/map_output_test.go
-26
internal/confyg/map_output_test.go
···
1
-
package confyg
2
-
3
-
import "testing"
4
-
5
-
func TestMapConfig(t *testing.T) {
6
-
mc := MapConfig{}
7
-
8
-
const configFile = `subscribe pewdiepie
9
-
10
-
unsubscribe (
11
-
t-series
12
-
)`
13
-
14
-
_, err := Parse("test.cfg", []byte(configFile), mc, mc)
15
-
if err != nil {
16
-
t.Fatal(err)
17
-
}
18
-
19
-
if mc["subscribe"][0] != "pewdiepie" {
20
-
t.Errorf("wanted subscribe->pewdiepie, got: %s", mc["subscribe"][0])
21
-
}
22
-
23
-
if mc["unsubscribe"][0] != "t-series" {
24
-
t.Errorf("wanted unsubscribe->t-series, got: %s", mc["unsubscribe"][0])
25
-
}
26
-
}
···
-164
internal/confyg/print.go
-164
internal/confyg/print.go
···
1
-
// Copyright 2018 The Go Authors. All rights reserved.
2
-
// Use of this source code is governed by a BSD-style
3
-
// license that can be found in the LICENSE file.
4
-
5
-
// Module file printer.
6
-
7
-
package confyg
8
-
9
-
import (
10
-
"bytes"
11
-
"fmt"
12
-
"strings"
13
-
)
14
-
15
-
func Format(f *FileSyntax) []byte {
16
-
pr := &printer{}
17
-
pr.file(f)
18
-
return pr.Bytes()
19
-
}
20
-
21
-
// A printer collects the state during printing of a file or expression.
22
-
type printer struct {
23
-
bytes.Buffer // output buffer
24
-
comment []Comment // pending end-of-line comments
25
-
margin int // left margin (indent), a number of tabs
26
-
}
27
-
28
-
// printf prints to the buffer.
29
-
func (p *printer) printf(format string, args ...interface{}) {
30
-
fmt.Fprintf(p, format, args...)
31
-
}
32
-
33
-
// indent returns the position on the current line, in bytes, 0-indexed.
34
-
func (p *printer) indent() int {
35
-
b := p.Bytes()
36
-
n := 0
37
-
for n < len(b) && b[len(b)-1-n] != '\n' {
38
-
n++
39
-
}
40
-
return n
41
-
}
42
-
43
-
// newline ends the current line, flushing end-of-line comments.
44
-
func (p *printer) newline() {
45
-
if len(p.comment) > 0 {
46
-
p.printf(" ")
47
-
for i, com := range p.comment {
48
-
if i > 0 {
49
-
p.trim()
50
-
p.printf("\n")
51
-
for i := 0; i < p.margin; i++ {
52
-
p.printf("\t")
53
-
}
54
-
}
55
-
p.printf("%s", strings.TrimSpace(com.Token))
56
-
}
57
-
p.comment = p.comment[:0]
58
-
}
59
-
60
-
p.trim()
61
-
p.printf("\n")
62
-
for i := 0; i < p.margin; i++ {
63
-
p.printf("\t")
64
-
}
65
-
}
66
-
67
-
// trim removes trailing spaces and tabs from the current line.
68
-
func (p *printer) trim() {
69
-
// Remove trailing spaces and tabs from line we're about to end.
70
-
b := p.Bytes()
71
-
n := len(b)
72
-
for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') {
73
-
n--
74
-
}
75
-
p.Truncate(n)
76
-
}
77
-
78
-
// file formats the given file into the print buffer.
79
-
func (p *printer) file(f *FileSyntax) {
80
-
for _, com := range f.Before {
81
-
p.printf("%s", strings.TrimSpace(com.Token))
82
-
p.newline()
83
-
}
84
-
85
-
for i, stmt := range f.Stmt {
86
-
switch x := stmt.(type) {
87
-
case *CommentBlock:
88
-
// comments already handled
89
-
p.expr(x)
90
-
91
-
default:
92
-
p.expr(x)
93
-
p.newline()
94
-
}
95
-
96
-
for _, com := range stmt.Comment().After {
97
-
p.printf("%s", strings.TrimSpace(com.Token))
98
-
p.newline()
99
-
}
100
-
101
-
if i+1 < len(f.Stmt) {
102
-
p.newline()
103
-
}
104
-
}
105
-
}
106
-
107
-
func (p *printer) expr(x Expr) {
108
-
// Emit line-comments preceding this expression.
109
-
if before := x.Comment().Before; len(before) > 0 {
110
-
// Want to print a line comment.
111
-
// Line comments must be at the current margin.
112
-
p.trim()
113
-
if p.indent() > 0 {
114
-
// There's other text on the line. Start a new line.
115
-
p.printf("\n")
116
-
}
117
-
// Re-indent to margin.
118
-
for i := 0; i < p.margin; i++ {
119
-
p.printf("\t")
120
-
}
121
-
for _, com := range before {
122
-
p.printf("%s", strings.TrimSpace(com.Token))
123
-
p.newline()
124
-
}
125
-
}
126
-
127
-
switch x := x.(type) {
128
-
default:
129
-
panic(fmt.Errorf("printer: unexpected type %T", x))
130
-
131
-
case *CommentBlock:
132
-
// done
133
-
134
-
case *LParen:
135
-
p.printf("(")
136
-
case *RParen:
137
-
p.printf(")")
138
-
139
-
case *Line:
140
-
sep := ""
141
-
for _, tok := range x.Token {
142
-
p.printf("%s%s", sep, tok)
143
-
sep = " "
144
-
}
145
-
146
-
case *LineBlock:
147
-
for _, tok := range x.Token {
148
-
p.printf("%s ", tok)
149
-
}
150
-
p.expr(&x.LParen)
151
-
p.margin++
152
-
for _, l := range x.Line {
153
-
p.newline()
154
-
p.expr(l)
155
-
}
156
-
p.margin--
157
-
p.newline()
158
-
p.expr(&x.RParen)
159
-
}
160
-
161
-
// Queue end-of-line comments for printing when we
162
-
// reach the end of the line.
163
-
p.comment = append(p.comment, x.Comment().Suffix...)
164
-
}
···
-678
internal/confyg/read.go
-678
internal/confyg/read.go
···
1
-
// Copyright 2018 The Go Authors. All rights reserved.
2
-
// Use of this source code is governed by a BSD-style
3
-
// license that can be found in the LICENSE file.
4
-
5
-
// Module file parser.
6
-
// This is a simplified copy of Google's buildifier parser.
7
-
8
-
package confyg
9
-
10
-
import (
11
-
"bytes"
12
-
"fmt"
13
-
"os"
14
-
"strings"
15
-
"unicode"
16
-
"unicode/utf8"
17
-
)
18
-
19
-
// A Position describes the position between two bytes of input.
20
-
type Position struct {
21
-
Line int // line in input (starting at 1)
22
-
LineRune int // rune in line (starting at 1)
23
-
Byte int // byte in input (starting at 0)
24
-
}
25
-
26
-
// add returns the position at the end of s, assuming it starts at p.
27
-
func (p Position) add(s string) Position {
28
-
p.Byte += len(s)
29
-
if n := strings.Count(s, "\n"); n > 0 {
30
-
p.Line += n
31
-
s = s[strings.LastIndex(s, "\n")+1:]
32
-
p.LineRune = 1
33
-
}
34
-
p.LineRune += utf8.RuneCountInString(s)
35
-
return p
36
-
}
37
-
38
-
// An Expr represents an input element.
39
-
type Expr interface {
40
-
// Span returns the start and end position of the expression,
41
-
// excluding leading or trailing comments.
42
-
Span() (start, end Position)
43
-
44
-
// Comment returns the comments attached to the expression.
45
-
// This method would normally be named 'Comments' but that
46
-
// would interfere with embedding a type of the same name.
47
-
Comment() *Comments
48
-
}
49
-
50
-
// A Comment represents a single // comment.
51
-
type Comment struct {
52
-
Start Position
53
-
Token string // without trailing newline
54
-
Suffix bool // an end of line (not whole line) comment
55
-
}
56
-
57
-
// Comments collects the comments associated with an expression.
58
-
type Comments struct {
59
-
Before []Comment // whole-line comments before this expression
60
-
Suffix []Comment // end-of-line comments after this expression
61
-
62
-
// For top-level expressions only, After lists whole-line
63
-
// comments following the expression.
64
-
After []Comment
65
-
}
66
-
67
-
// Comment returns the receiver. This isn't useful by itself, but
68
-
// a Comments struct is embedded into all the expression
69
-
// implementation types, and this gives each of those a Comment
70
-
// method to satisfy the Expr interface.
71
-
func (c *Comments) Comment() *Comments {
72
-
return c
73
-
}
74
-
75
-
// A FileSyntax represents an entire go.mod file.
76
-
type FileSyntax struct {
77
-
Name string // file path
78
-
Comments
79
-
Stmt []Expr
80
-
}
81
-
82
-
func (x *FileSyntax) Span() (start, end Position) {
83
-
if len(x.Stmt) == 0 {
84
-
return
85
-
}
86
-
start, _ = x.Stmt[0].Span()
87
-
_, end = x.Stmt[len(x.Stmt)-1].Span()
88
-
return start, end
89
-
}
90
-
91
-
// A CommentBlock represents a top-level block of comments separate
92
-
// from any rule.
93
-
type CommentBlock struct {
94
-
Comments
95
-
Start Position
96
-
}
97
-
98
-
func (x *CommentBlock) Span() (start, end Position) {
99
-
return x.Start, x.Start
100
-
}
101
-
102
-
// A Line is a single line of tokens.
103
-
type Line struct {
104
-
Comments
105
-
Start Position
106
-
Token []string
107
-
End Position
108
-
}
109
-
110
-
func (x *Line) Span() (start, end Position) {
111
-
return x.Start, x.End
112
-
}
113
-
114
-
// A LineBlock is a factored block of lines, like
115
-
//
116
-
// require (
117
-
// "x"
118
-
// "y"
119
-
// )
120
-
type LineBlock struct {
121
-
Comments
122
-
Start Position
123
-
LParen LParen
124
-
Token []string
125
-
Line []*Line
126
-
RParen RParen
127
-
}
128
-
129
-
func (x *LineBlock) Span() (start, end Position) {
130
-
return x.Start, x.RParen.Pos.add(")")
131
-
}
132
-
133
-
// An LParen represents the beginning of a parenthesized line block.
134
-
// It is a place to store suffix comments.
135
-
type LParen struct {
136
-
Comments
137
-
Pos Position
138
-
}
139
-
140
-
func (x *LParen) Span() (start, end Position) {
141
-
return x.Pos, x.Pos.add(")")
142
-
}
143
-
144
-
// An RParen represents the end of a parenthesized line block.
145
-
// It is a place to store whole-line (before) comments.
146
-
type RParen struct {
147
-
Comments
148
-
Pos Position
149
-
}
150
-
151
-
func (x *RParen) Span() (start, end Position) {
152
-
return x.Pos, x.Pos.add(")")
153
-
}
154
-
155
-
// An input represents a single input file being parsed.
156
-
type input struct {
157
-
// Lexing state.
158
-
filename string // name of input file, for errors
159
-
complete []byte // entire input
160
-
remaining []byte // remaining input
161
-
token []byte // token being scanned
162
-
lastToken string // most recently returned token, for error messages
163
-
pos Position // current input position
164
-
comments []Comment // accumulated comments
165
-
166
-
// Parser state.
167
-
file *FileSyntax // returned top-level syntax tree
168
-
parseError error // error encountered during parsing
169
-
170
-
// Comment assignment state.
171
-
pre []Expr // all expressions, in preorder traversal
172
-
post []Expr // all expressions, in postorder traversal
173
-
}
174
-
175
-
func newInput(filename string, data []byte) *input {
176
-
return &input{
177
-
filename: filename,
178
-
complete: data,
179
-
remaining: data,
180
-
pos: Position{Line: 1, LineRune: 1, Byte: 0},
181
-
}
182
-
}
183
-
184
-
// parse parses the input file.
185
-
func parse(file string, data []byte) (f *FileSyntax, err error) {
186
-
in := newInput(file, data)
187
-
// The parser panics for both routine errors like syntax errors
188
-
// and for programmer bugs like array index errors.
189
-
// Turn both into error returns. Catching bug panics is
190
-
// especially important when processing many files.
191
-
defer func() {
192
-
if e := recover(); e != nil {
193
-
if e == in.parseError {
194
-
err = in.parseError
195
-
} else {
196
-
err = fmt.Errorf("%s:%d:%d: internal error: %v", in.filename, in.pos.Line, in.pos.LineRune, e)
197
-
}
198
-
}
199
-
}()
200
-
201
-
// Invoke the parser.
202
-
in.parseFile()
203
-
if in.parseError != nil {
204
-
return nil, in.parseError
205
-
}
206
-
in.file.Name = in.filename
207
-
208
-
// Assign comments to nearby syntax.
209
-
in.assignComments()
210
-
211
-
return in.file, nil
212
-
}
213
-
214
-
// Error is called to report an error.
215
-
// The reason s is often "syntax error".
216
-
// Error does not return: it panics.
217
-
func (in *input) Error(s string) {
218
-
if s == "syntax error" && in.lastToken != "" {
219
-
s += " near " + in.lastToken
220
-
}
221
-
in.parseError = fmt.Errorf("%s:%d:%d: %v", in.filename, in.pos.Line, in.pos.LineRune, s)
222
-
panic(in.parseError)
223
-
}
224
-
225
-
// eof reports whether the input has reached end of file.
226
-
func (in *input) eof() bool {
227
-
return len(in.remaining) == 0
228
-
}
229
-
230
-
// peekRune returns the next rune in the input without consuming it.
231
-
func (in *input) peekRune() int {
232
-
if len(in.remaining) == 0 {
233
-
return 0
234
-
}
235
-
r, _ := utf8.DecodeRune(in.remaining)
236
-
return int(r)
237
-
}
238
-
239
-
// readRune consumes and returns the next rune in the input.
240
-
func (in *input) readRune() int {
241
-
if len(in.remaining) == 0 {
242
-
in.Error("internal lexer error: readRune at EOF")
243
-
}
244
-
r, size := utf8.DecodeRune(in.remaining)
245
-
in.remaining = in.remaining[size:]
246
-
if r == '\n' {
247
-
in.pos.Line++
248
-
in.pos.LineRune = 1
249
-
} else {
250
-
in.pos.LineRune++
251
-
}
252
-
in.pos.Byte += size
253
-
return int(r)
254
-
}
255
-
256
-
type symType struct {
257
-
pos Position
258
-
endPos Position
259
-
text string
260
-
}
261
-
262
-
// startToken marks the beginning of the next input token.
263
-
// It must be followed by a call to endToken, once the token has
264
-
// been consumed using readRune.
265
-
func (in *input) startToken(sym *symType) {
266
-
in.token = in.remaining
267
-
sym.text = ""
268
-
sym.pos = in.pos
269
-
}
270
-
271
-
// endToken marks the end of an input token.
272
-
// It records the actual token string in sym.text if the caller
273
-
// has not done that already.
274
-
func (in *input) endToken(sym *symType) {
275
-
if sym.text == "" {
276
-
tok := string(in.token[:len(in.token)-len(in.remaining)])
277
-
sym.text = tok
278
-
in.lastToken = sym.text
279
-
}
280
-
sym.endPos = in.pos
281
-
}
282
-
283
-
// lex is called from the parser to obtain the next input token.
284
-
// It returns the token value (either a rune like '+' or a symbolic token _FOR)
285
-
// and sets val to the data associated with the token.
286
-
// For all our input tokens, the associated data is
287
-
// val.Pos (the position where the token begins)
288
-
// and val.Token (the input string corresponding to the token).
289
-
func (in *input) lex(sym *symType) int {
290
-
// Skip past spaces, stopping at non-space or EOF.
291
-
countNL := 0 // number of newlines we've skipped past
292
-
for !in.eof() {
293
-
// Skip over spaces. Count newlines so we can give the parser
294
-
// information about where top-level blank lines are,
295
-
// for top-level comment assignment.
296
-
c := in.peekRune()
297
-
if c == ' ' || c == '\t' || c == '\r' {
298
-
in.readRune()
299
-
continue
300
-
}
301
-
302
-
// Comment runs to end of line.
303
-
if c == '#' {
304
-
in.startToken(sym)
305
-
306
-
// Is this comment the only thing on its line?
307
-
// Find the last \n before this // and see if it's all
308
-
// spaces from there to here.
309
-
i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
310
-
suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
311
-
in.readRune()
312
-
c = in.peekRune()
313
-
if c != '#' {
314
-
in.Error(fmt.Sprintf("unexpected input character %#q", c))
315
-
}
316
-
317
-
// Consume comment.
318
-
for len(in.remaining) > 0 && in.readRune() != '\n' {
319
-
}
320
-
in.endToken(sym)
321
-
322
-
sym.text = strings.TrimRight(sym.text, "\n")
323
-
in.lastToken = "comment"
324
-
325
-
// If we are at top level (not in a statement), hand the comment to
326
-
// the parser as a _COMMENT token. The grammar is written
327
-
// to handle top-level comments itself.
328
-
if !suffix {
329
-
// Not in a statement. Tell parser about top-level comment.
330
-
return _COMMENT
331
-
}
332
-
333
-
// Otherwise, save comment for later attachment to syntax tree.
334
-
if countNL > 1 {
335
-
in.comments = append(in.comments, Comment{sym.pos, "", false})
336
-
}
337
-
in.comments = append(in.comments, Comment{sym.pos, sym.text, suffix})
338
-
countNL = 1
339
-
return _EOL
340
-
}
341
-
342
-
// Found non-space non-comment.
343
-
break
344
-
}
345
-
346
-
// Found the beginning of the next token.
347
-
in.startToken(sym)
348
-
defer in.endToken(sym)
349
-
350
-
// End of file.
351
-
if in.eof() {
352
-
in.lastToken = "EOF"
353
-
return _EOF
354
-
}
355
-
356
-
// Punctuation tokens.
357
-
switch c := in.peekRune(); c {
358
-
case '\n':
359
-
in.readRune()
360
-
return c
361
-
362
-
case '(':
363
-
in.readRune()
364
-
return c
365
-
366
-
case ')':
367
-
in.readRune()
368
-
return c
369
-
370
-
case '"', '`': // quoted string
371
-
quote := c
372
-
in.readRune()
373
-
for {
374
-
if in.eof() {
375
-
in.pos = sym.pos
376
-
in.Error("unexpected EOF in string")
377
-
}
378
-
if in.peekRune() == '\n' {
379
-
in.Error("unexpected newline in string")
380
-
}
381
-
c := in.readRune()
382
-
if c == quote {
383
-
break
384
-
}
385
-
if c == '\\' && quote != '`' {
386
-
if in.eof() {
387
-
in.pos = sym.pos
388
-
in.Error("unexpected EOF in string")
389
-
}
390
-
in.readRune()
391
-
}
392
-
}
393
-
in.endToken(sym)
394
-
return _STRING
395
-
}
396
-
397
-
// Checked all punctuation. Must be identifier token.
398
-
if c := in.peekRune(); !isIdent(c) {
399
-
in.Error(fmt.Sprintf("unexpected input character %#q", c))
400
-
}
401
-
402
-
// Scan over identifier.
403
-
for isIdent(in.peekRune()) {
404
-
in.readRune()
405
-
}
406
-
return _IDENT
407
-
}
408
-
409
-
// isIdent reports whether c is an identifier rune.
410
-
// We treat nearly all runes as identifier runes.
411
-
func isIdent(c int) bool {
412
-
return c != 0 && !unicode.IsSpace(rune(c)) && c != '(' && c != ')' && c != '"' && c != '`'
413
-
}
414
-
415
-
// Comment assignment.
416
-
// We build two lists of all subexpressions, preorder and postorder.
417
-
// The preorder list is ordered by start location, with outer expressions first.
418
-
// The postorder list is ordered by end location, with outer expressions last.
419
-
// We use the preorder list to assign each whole-line comment to the syntax
420
-
// immediately following it, and we use the postorder list to assign each
421
-
// end-of-line comment to the syntax immediately preceding it.
422
-
423
-
// order walks the expression adding it and its subexpressions to the
424
-
// preorder and postorder lists.
425
-
func (in *input) order(x Expr) {
426
-
if x != nil {
427
-
in.pre = append(in.pre, x)
428
-
}
429
-
switch x := x.(type) {
430
-
default:
431
-
panic(fmt.Errorf("order: unexpected type %T", x))
432
-
case nil:
433
-
// nothing
434
-
case *LParen, *RParen:
435
-
// nothing
436
-
case *CommentBlock:
437
-
// nothing
438
-
case *Line:
439
-
// nothing
440
-
case *FileSyntax:
441
-
for _, stmt := range x.Stmt {
442
-
in.order(stmt)
443
-
}
444
-
case *LineBlock:
445
-
in.order(&x.LParen)
446
-
for _, l := range x.Line {
447
-
in.order(l)
448
-
}
449
-
in.order(&x.RParen)
450
-
}
451
-
if x != nil {
452
-
in.post = append(in.post, x)
453
-
}
454
-
}
455
-
456
-
// assignComments attaches comments to nearby syntax.
457
-
func (in *input) assignComments() {
458
-
const debug = false
459
-
460
-
// Generate preorder and postorder lists.
461
-
in.order(in.file)
462
-
463
-
// Split into whole-line comments and suffix comments.
464
-
var line, suffix []Comment
465
-
for _, com := range in.comments {
466
-
if com.Suffix {
467
-
suffix = append(suffix, com)
468
-
} else {
469
-
line = append(line, com)
470
-
}
471
-
}
472
-
473
-
if debug {
474
-
for _, c := range line {
475
-
fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
476
-
}
477
-
}
478
-
479
-
// Assign line comments to syntax immediately following.
480
-
for _, x := range in.pre {
481
-
start, _ := x.Span()
482
-
if debug {
483
-
fmt.Printf("pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
484
-
}
485
-
xcom := x.Comment()
486
-
for len(line) > 0 && start.Byte >= line[0].Start.Byte {
487
-
if debug {
488
-
fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
489
-
}
490
-
xcom.Before = append(xcom.Before, line[0])
491
-
line = line[1:]
492
-
}
493
-
}
494
-
495
-
// Remaining line comments go at end of file.
496
-
in.file.After = append(in.file.After, line...)
497
-
498
-
if debug {
499
-
for _, c := range suffix {
500
-
fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
501
-
}
502
-
}
503
-
504
-
// Assign suffix comments to syntax immediately before.
505
-
for i := len(in.post) - 1; i >= 0; i-- {
506
-
x := in.post[i]
507
-
508
-
start, end := x.Span()
509
-
if debug {
510
-
fmt.Printf("post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
511
-
}
512
-
513
-
// Do not assign suffix comments to end of line block or whole file.
514
-
// Instead assign them to the last element inside.
515
-
switch x.(type) {
516
-
case *FileSyntax:
517
-
continue
518
-
}
519
-
520
-
// Do not assign suffix comments to something that starts
521
-
// on an earlier line, so that in
522
-
//
523
-
// x ( y
524
-
// z ) // comment
525
-
//
526
-
// we assign the comment to z and not to x ( ... ).
527
-
if start.Line != end.Line {
528
-
continue
529
-
}
530
-
xcom := x.Comment()
531
-
for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
532
-
if debug {
533
-
fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
534
-
}
535
-
xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
536
-
suffix = suffix[:len(suffix)-1]
537
-
}
538
-
}
539
-
540
-
// We assigned suffix comments in reverse.
541
-
// If multiple suffix comments were appended to the same
542
-
// expression node, they are now in reverse. Fix that.
543
-
for _, x := range in.post {
544
-
reverseComments(x.Comment().Suffix)
545
-
}
546
-
547
-
// Remaining suffix comments go at beginning of file.
548
-
in.file.Before = append(in.file.Before, suffix...)
549
-
}
550
-
551
-
// reverseComments reverses the []Comment list.
552
-
func reverseComments(list []Comment) {
553
-
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
554
-
list[i], list[j] = list[j], list[i]
555
-
}
556
-
}
557
-
558
-
func (in *input) parseFile() {
559
-
in.file = new(FileSyntax)
560
-
var sym symType
561
-
var cb *CommentBlock
562
-
for {
563
-
tok := in.lex(&sym)
564
-
switch tok {
565
-
case '\n':
566
-
if cb != nil {
567
-
in.file.Stmt = append(in.file.Stmt, cb)
568
-
cb = nil
569
-
}
570
-
case _COMMENT:
571
-
if cb == nil {
572
-
cb = &CommentBlock{Start: sym.pos}
573
-
}
574
-
com := cb.Comment()
575
-
com.Before = append(com.Before, Comment{Start: sym.pos, Token: sym.text})
576
-
case _EOF:
577
-
if cb != nil {
578
-
in.file.Stmt = append(in.file.Stmt, cb)
579
-
}
580
-
return
581
-
default:
582
-
in.parseStmt(&sym)
583
-
if cb != nil {
584
-
in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
585
-
cb = nil
586
-
}
587
-
}
588
-
}
589
-
}
590
-
591
-
func (in *input) parseStmt(sym *symType) {
592
-
start := sym.pos
593
-
end := sym.endPos
594
-
token := []string{sym.text}
595
-
for {
596
-
tok := in.lex(sym)
597
-
switch tok {
598
-
case '\n', _EOF, _EOL:
599
-
in.file.Stmt = append(in.file.Stmt, &Line{
600
-
Start: start,
601
-
Token: token,
602
-
End: end,
603
-
})
604
-
return
605
-
case '(':
606
-
in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, token, sym))
607
-
return
608
-
default:
609
-
token = append(token, sym.text)
610
-
end = sym.endPos
611
-
}
612
-
}
613
-
}
614
-
615
-
func (in *input) parseLineBlock(start Position, token []string, sym *symType) *LineBlock {
616
-
x := &LineBlock{
617
-
Start: start,
618
-
Token: token,
619
-
LParen: LParen{Pos: sym.pos},
620
-
}
621
-
var comments []Comment
622
-
for {
623
-
tok := in.lex(sym)
624
-
switch tok {
625
-
case _EOL:
626
-
// ignore
627
-
case '\n':
628
-
if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
629
-
comments = append(comments, Comment{})
630
-
}
631
-
case _COMMENT:
632
-
comments = append(comments, Comment{Start: sym.pos, Token: sym.text})
633
-
case _EOF:
634
-
in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
635
-
case ')':
636
-
x.RParen.Before = comments
637
-
x.RParen.Pos = sym.pos
638
-
tok = in.lex(sym)
639
-
if tok != '\n' && tok != _EOF && tok != _EOL {
640
-
in.Error("syntax error (expected newline after closing paren)")
641
-
}
642
-
return x
643
-
default:
644
-
l := in.parseLine(sym)
645
-
x.Line = append(x.Line, l)
646
-
l.Comment().Before = comments
647
-
comments = nil
648
-
}
649
-
}
650
-
}
651
-
652
-
func (in *input) parseLine(sym *symType) *Line {
653
-
start := sym.pos
654
-
end := sym.endPos
655
-
token := []string{sym.text}
656
-
for {
657
-
tok := in.lex(sym)
658
-
switch tok {
659
-
case '\n', _EOF, _EOL:
660
-
return &Line{
661
-
Start: start,
662
-
Token: token,
663
-
End: end,
664
-
}
665
-
default:
666
-
token = append(token, sym.text)
667
-
end = sym.endPos
668
-
}
669
-
}
670
-
}
671
-
672
-
const (
673
-
_EOF = -(1 + iota)
674
-
_EOL
675
-
_IDENT
676
-
_STRING
677
-
_COMMENT
678
-
)
···
-95
internal/confyg/read_test.go
-95
internal/confyg/read_test.go
···
1
-
// Copyright 2018 The Go Authors. All rights reserved.
2
-
// Use of this source code is governed by a BSD-style
3
-
// license that can be found in the LICENSE file.
4
-
5
-
package confyg
6
-
7
-
import (
8
-
"bytes"
9
-
"os"
10
-
"os/exec"
11
-
"path/filepath"
12
-
"testing"
13
-
)
14
-
15
-
// Test that reading and then writing the golden files
16
-
// does not change their output.
17
-
func TestPrintGolden(t *testing.T) {
18
-
outs, err := filepath.Glob("testdata/*.golden")
19
-
if err != nil {
20
-
t.Fatal(err)
21
-
}
22
-
for _, out := range outs {
23
-
testPrint(t, out, out)
24
-
}
25
-
}
26
-
27
-
// testPrint is a helper for testing the printer.
28
-
// It reads the file named in, reformats it, and compares
29
-
// the result to the file named out.
30
-
func testPrint(t *testing.T, in, out string) {
31
-
data, err := os.ReadFile(in)
32
-
if err != nil {
33
-
t.Error(err)
34
-
return
35
-
}
36
-
37
-
golden, err := os.ReadFile(out)
38
-
if err != nil {
39
-
t.Error(err)
40
-
return
41
-
}
42
-
43
-
base := "testdata/" + filepath.Base(in)
44
-
f, err := parse(in, data)
45
-
if err != nil {
46
-
t.Error(err)
47
-
return
48
-
}
49
-
50
-
ndata := Format(f)
51
-
52
-
if !bytes.Equal(ndata, golden) {
53
-
t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base)
54
-
tdiff(t, string(golden), string(ndata))
55
-
return
56
-
}
57
-
}
58
-
59
-
// diff returns the output of running diff on b1 and b2.
60
-
func diff(b1, b2 []byte) (data []byte, err error) {
61
-
f1, err := os.CreateTemp("", "testdiff")
62
-
if err != nil {
63
-
return nil, err
64
-
}
65
-
defer os.Remove(f1.Name())
66
-
defer f1.Close()
67
-
68
-
f2, err := os.CreateTemp("", "testdiff")
69
-
if err != nil {
70
-
return nil, err
71
-
}
72
-
defer os.Remove(f2.Name())
73
-
defer f2.Close()
74
-
75
-
f1.Write(b1)
76
-
f2.Write(b2)
77
-
78
-
data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
79
-
if len(data) > 0 {
80
-
// diff exits with a non-zero status when the files don't match.
81
-
// Ignore that failure as long as we get output.
82
-
err = nil
83
-
}
84
-
return
85
-
}
86
-
87
-
// tdiff logs the diff output to t.Error.
88
-
func tdiff(t *testing.T, a, b string) {
89
-
data, err := diff([]byte(a), []byte(b))
90
-
if err != nil {
91
-
t.Error(err)
92
-
return
93
-
}
94
-
t.Error(string(data))
95
-
}
···
-19
internal/confyg/reader.go
-19
internal/confyg/reader.go
···
1
-
package confyg
2
-
3
-
import "bytes"
4
-
5
-
// Reader is called when individual lines of the configuration file are being read.
6
-
// This is where you should populate any relevant structures with information.
7
-
//
8
-
// If something goes wrong in the file parsing step, add data to the errs buffer
9
-
// describing what went wrong.
10
-
type Reader interface {
11
-
Read(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string)
12
-
}
13
-
14
-
// ReaderFunc implements Reader for inline definitions.
15
-
type ReaderFunc func(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string)
16
-
17
-
func (r ReaderFunc) Read(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string) {
18
-
r(errs, fs, line, verb, args)
19
-
}
···
-59
internal/confyg/reader_test.go
-59
internal/confyg/reader_test.go
···
1
-
package confyg
2
-
3
-
import (
4
-
"bytes"
5
-
"fmt"
6
-
"testing"
7
-
)
8
-
9
-
func TestReader(t *testing.T) {
10
-
done := false
11
-
acc := 0
12
-
13
-
al := AllowerFunc(func(verb string, block bool) bool {
14
-
switch verb {
15
-
case "test":
16
-
return !block
17
-
18
-
case "acc":
19
-
return true
20
-
default:
21
-
return false
22
-
}
23
-
})
24
-
25
-
r := ReaderFunc(func(errs *bytes.Buffer, fs *FileSyntax, line *Line, verb string, args []string) {
26
-
switch verb {
27
-
case "test":
28
-
done = len(args) == 1
29
-
case "acc":
30
-
acc++
31
-
default:
32
-
fmt.Fprintf(errs, "%s:%d unknown verb %s\n", fs.Name, line.Start.Line, verb)
33
-
}
34
-
})
35
-
const configFile = `test "42"
36
-
37
-
acc (
38
-
1
39
-
2
40
-
3
41
-
)`
42
-
43
-
fs, err := Parse("test.cfg", []byte(configFile), r, al)
44
-
if err != nil {
45
-
t.Fatal(err)
46
-
}
47
-
48
-
_ = fs
49
-
50
-
t.Logf("done: %v", done)
51
-
if !done {
52
-
t.Fatal("done was not flagged")
53
-
}
54
-
55
-
t.Logf("acc: %v", acc)
56
-
if acc != 3 {
57
-
t.Fatal("acc was not changed")
58
-
}
59
-
}
···
-53
internal/confyg/rule.go
-53
internal/confyg/rule.go
···
1
-
// Copyright 2018 The Go Authors. All rights reserved.
2
-
// Use of this source code is governed by a BSD-style
3
-
// license that can be found in the LICENSE file.
4
-
5
-
package confyg
6
-
7
-
import (
8
-
"bytes"
9
-
"errors"
10
-
"fmt"
11
-
"strings"
12
-
)
13
-
14
-
func Parse(file string, data []byte, r Reader, al Allower) (*FileSyntax, error) {
15
-
fs, err := parse(file, data)
16
-
if err != nil {
17
-
return nil, err
18
-
}
19
-
20
-
var errs bytes.Buffer
21
-
for _, x := range fs.Stmt {
22
-
switch x := x.(type) {
23
-
case *Line:
24
-
ok := al.Allow(x.Token[0], false)
25
-
if ok {
26
-
r.Read(&errs, fs, x, x.Token[0], x.Token[1:])
27
-
continue
28
-
}
29
-
30
-
fmt.Fprintf(&errs, "%s:%d: can't allow line verb %s", file, x.Start.Line, x.Token[0])
31
-
32
-
case *LineBlock:
33
-
if len(x.Token) > 1 {
34
-
fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
35
-
continue
36
-
}
37
-
ok := al.Allow(x.Token[0], true)
38
-
if ok {
39
-
for _, l := range x.Line {
40
-
r.Read(&errs, fs, l, x.Token[0], l.Token)
41
-
}
42
-
continue
43
-
}
44
-
45
-
fmt.Fprintf(&errs, "%s:%d: can't allow line block verb %s", file, x.Start.Line, x.Token[0])
46
-
}
47
-
}
48
-
49
-
if errs.Len() > 0 {
50
-
return nil, errors.New(strings.TrimRight(errs.String(), "\n"))
51
-
}
52
-
return fs, nil
53
-
}
···
-29
internal/confyg/testdata/block.golden
-29
internal/confyg/testdata/block.golden
···
1
-
## comment
2
-
x "y" z
3
-
4
-
## block
5
-
block ( ## block-eol
6
-
## x-before-line
7
-
8
-
"x" ( y ## x-eol
9
-
"x1"
10
-
"x2"
11
-
## line
12
-
"x3"
13
-
"x4"
14
-
15
-
"x5"
16
-
17
-
## y-line
18
-
"y" ## y-eol
19
-
20
-
"z" ## z-eol
21
-
) ## block-eol2
22
-
23
-
block2 (
24
-
x
25
-
y
26
-
z
27
-
)
28
-
29
-
## eof
···
-29
internal/confyg/testdata/block.in
-29
internal/confyg/testdata/block.in
···
1
-
## comment
2
-
x "y" z
3
-
4
-
## block
5
-
block ( ## block-eol
6
-
## x-before-line
7
-
8
-
"x" ( y ## x-eol
9
-
"x1"
10
-
"x2"
11
-
## line
12
-
"x3"
13
-
"x4"
14
-
15
-
"x5"
16
-
17
-
## y-line
18
-
"y" ## y-eol
19
-
20
-
"z" ## z-eol
21
-
) ## block-eol2
22
-
23
-
24
-
block2 (x
25
-
y
26
-
z
27
-
)
28
-
29
-
## eof
···
-10
internal/confyg/testdata/comment.golden
-10
internal/confyg/testdata/comment.golden
-8
internal/confyg/testdata/comment.in
-8
internal/confyg/testdata/comment.in
internal/confyg/testdata/empty.golden
internal/confyg/testdata/empty.golden
This is a binary file and will not be displayed.
internal/confyg/testdata/empty.in
internal/confyg/testdata/empty.in
This is a binary file and will not be displayed.
-1
internal/confyg/testdata/module.golden
-1
internal/confyg/testdata/module.golden
···
1
-
module "abc"
···
-1
internal/confyg/testdata/module.in
-1
internal/confyg/testdata/module.in
···
1
-
module "abc"
···
-5
internal/confyg/testdata/replace.golden
-5
internal/confyg/testdata/replace.golden
-5
internal/confyg/testdata/replace.in
-5
internal/confyg/testdata/replace.in
-6
internal/confyg/testdata/replace2.golden
-6
internal/confyg/testdata/replace2.golden
-6
internal/confyg/testdata/replace2.in
-6
internal/confyg/testdata/replace2.in
-7
internal/confyg/testdata/rule1.golden
-7
internal/confyg/testdata/rule1.golden
-1
internal/confyg/testdata/url.golden
-1
internal/confyg/testdata/url.golden
···
1
-
url https://foo.bar
···
+36
-5
internal/git.go
+36
-5
internal/git.go
···
3
import (
4
"context"
5
"flag"
6
"os"
7
"path/filepath"
8
9
"github.com/Songmu/gitconfig"
10
"github.com/TecharoHQ/yeet/internal/yeet"
11
)
12
13
var (
14
-
GPGKeyFile = flag.String("gpg-key-file", gpgKeyFileLocation(), "GPG key file to sign the package")
15
-
GPGKeyID = flag.String("gpg-key-id", "", "GPG key ID to sign the package")
16
-
GPGKeyPassword = flag.String("gpg-key-password", "", "GPG key password to sign the package")
17
-
UserName = flag.String("git-user-name", GitUserName(), "user name in Git")
18
-
UserEmail = flag.String("git-user-email", GitUserEmail(), "user email in Git")
19
)
20
21
const (
···
58
59
return vers
60
}
···
3
import (
4
"context"
5
"flag"
6
+
"log/slog"
7
"os"
8
+
"os/exec"
9
"path/filepath"
10
+
"strconv"
11
+
"strings"
12
+
"time"
13
14
"github.com/Songmu/gitconfig"
15
"github.com/TecharoHQ/yeet/internal/yeet"
16
)
17
18
var (
19
+
GPGKeyFile = flag.String("gpg-key-file", gpgKeyFileLocation(), "GPG key file to sign the package")
20
+
GPGKeyID = flag.String("gpg-key-id", "", "GPG key ID to sign the package")
21
+
GPGKeyPassword = flag.String("gpg-key-password", "", "GPG key password to sign the package")
22
+
UserName = flag.String("git-user-name", GitUserName(), "user name in Git")
23
+
UserEmail = flag.String("git-user-email", GitUserEmail(), "user email in Git")
24
+
SourceDateEpoch = flag.Int64("source-date-epoch", GetSourceDateEpoch(), "Timestamp to use for all files in packages")
25
)
26
27
const (
···
64
65
return vers
66
}
67
+
68
+
func GetSourceDateEpoch() int64 {
69
+
// fallback needs to be 1 because some software thinks unix time 0 means "no time"
70
+
const fallback = 1
71
+
72
+
gitPath, err := exec.LookPath("git")
73
+
if err != nil {
74
+
slog.Warn("git not found in $PATH", "err", err)
75
+
return fallback
76
+
}
77
+
78
+
epochFromGitStr, err := yeet.Output(context.Background(), gitPath, "log", "-1", "--format=%ct")
79
+
if err == nil {
80
+
num, _ := strconv.ParseInt(strings.TrimSpace(epochFromGitStr), 10, 64)
81
+
if num != 0 {
82
+
return num
83
+
}
84
+
}
85
+
86
+
return fallback
87
+
}
88
+
89
+
func SourceEpoch() time.Time {
90
+
return time.Unix(*SourceDateEpoch, 0)
91
+
}
+15
-6
internal/mkdeb/mkdeb.go
+15
-6
internal/mkdeb/mkdeb.go
···
6
"os"
7
"path/filepath"
8
"runtime"
9
-
"time"
10
11
"github.com/Masterminds/semver/v3"
12
"github.com/TecharoHQ/yeet/internal"
···
76
Type: files.TypeDir,
77
Destination: d,
78
FileInfo: &files.ContentFileInfo{
79
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
80
Mode: os.FileMode(0600),
81
},
82
})
···
89
Destination: osPath,
90
FileInfo: &files.ContentFileInfo{
91
Mode: os.FileMode(0600),
92
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
93
},
94
})
95
}
···
100
Source: repoPath,
101
Destination: filepath.Join("/usr/share/doc", p.Name, rpmPath),
102
FileInfo: &files.ContentFileInfo{
103
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
104
},
105
})
106
}
···
111
Source: repoPath,
112
Destination: rpmPath,
113
FileInfo: &files.ContentFileInfo{
114
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
115
},
116
})
117
}
···
130
Source: path,
131
Destination: path[len(dir)+1:],
132
FileInfo: &files.ContentFileInfo{
133
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
134
},
135
})
136
···
139
return "", fmt.Errorf("mkdeb: can't walk output directory: %w", err)
140
}
141
142
info := nfpm.WithDefaults(&nfpm.Info{
143
Name: p.Name,
144
Version: p.Version,
···
148
Maintainer: fmt.Sprintf("%s <%s>", *internal.UserName, *internal.UserEmail),
149
Homepage: p.Homepage,
150
License: p.License,
151
Overridables: nfpm.Overridables{
152
Contents: contents,
153
Depends: p.Depends,
···
6
"os"
7
"path/filepath"
8
"runtime"
9
10
"github.com/Masterminds/semver/v3"
11
"github.com/TecharoHQ/yeet/internal"
···
75
Type: files.TypeDir,
76
Destination: d,
77
FileInfo: &files.ContentFileInfo{
78
+
MTime: internal.SourceEpoch(),
79
Mode: os.FileMode(0600),
80
},
81
})
···
88
Destination: osPath,
89
FileInfo: &files.ContentFileInfo{
90
Mode: os.FileMode(0600),
91
+
MTime: internal.SourceEpoch(),
92
},
93
})
94
}
···
99
Source: repoPath,
100
Destination: filepath.Join("/usr/share/doc", p.Name, rpmPath),
101
FileInfo: &files.ContentFileInfo{
102
+
MTime: internal.SourceEpoch(),
103
},
104
})
105
}
···
110
Source: repoPath,
111
Destination: rpmPath,
112
FileInfo: &files.ContentFileInfo{
113
+
MTime: internal.SourceEpoch(),
114
},
115
})
116
}
···
129
Source: path,
130
Destination: path[len(dir)+1:],
131
FileInfo: &files.ContentFileInfo{
132
+
MTime: internal.SourceEpoch(),
133
},
134
})
135
···
138
return "", fmt.Errorf("mkdeb: can't walk output directory: %w", err)
139
}
140
141
+
contents, err = files.PrepareForPackager(contents, 0o002, "deb", true, internal.SourceEpoch())
142
+
if err != nil {
143
+
return "", fmt.Errorf("mkdeb: can't prepare for packager: %w", err)
144
+
}
145
+
146
+
for _, content := range contents {
147
+
content.FileInfo.MTime = internal.SourceEpoch()
148
+
}
149
+
150
info := nfpm.WithDefaults(&nfpm.Info{
151
Name: p.Name,
152
Version: p.Version,
···
156
Maintainer: fmt.Sprintf("%s <%s>", *internal.UserName, *internal.UserEmail),
157
Homepage: p.Homepage,
158
License: p.License,
159
+
MTime: internal.SourceEpoch(),
160
Overridables: nfpm.Overridables{
161
Contents: contents,
162
Depends: p.Depends,
+16
-5
internal/mkrpm/mkrpm.go
+16
-5
internal/mkrpm/mkrpm.go
···
80
Type: files.TypeDir,
81
Destination: d,
82
FileInfo: &files.ContentFileInfo{
83
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
84
},
85
})
86
}
···
92
Destination: rpmPath,
93
FileInfo: &files.ContentFileInfo{
94
Mode: os.FileMode(0600),
95
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
96
},
97
})
98
}
···
103
Source: repoPath,
104
Destination: filepath.Join("/usr/share/doc", p.Name, rpmPath),
105
FileInfo: &files.ContentFileInfo{
106
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
107
},
108
})
109
}
···
114
Source: repoPath,
115
Destination: rpmPath,
116
FileInfo: &files.ContentFileInfo{
117
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
118
},
119
})
120
}
···
133
Source: path,
134
Destination: path[len(dir)+1:],
135
FileInfo: &files.ContentFileInfo{
136
-
MTime: time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC),
137
},
138
})
139
···
142
return "", fmt.Errorf("mkrpm: can't walk output directory: %w", err)
143
}
144
145
info := nfpm.WithDefaults(&nfpm.Info{
146
Name: p.Name,
147
Version: p.Version,
···
151
Maintainer: fmt.Sprintf("%s <%s>", *internal.UserName, *internal.UserEmail),
152
Homepage: p.Homepage,
153
License: p.License,
154
Overridables: nfpm.Overridables{
155
Contents: contents,
156
Depends: p.Depends,
···
160
},
161
})
162
163
info.Overridables.RPM.Group = p.Group
164
165
if *internal.GPGKeyPassword != "" {
···
80
Type: files.TypeDir,
81
Destination: d,
82
FileInfo: &files.ContentFileInfo{
83
+
MTime: internal.SourceEpoch(),
84
},
85
})
86
}
···
92
Destination: rpmPath,
93
FileInfo: &files.ContentFileInfo{
94
Mode: os.FileMode(0600),
95
+
MTime: internal.SourceEpoch(),
96
},
97
})
98
}
···
103
Source: repoPath,
104
Destination: filepath.Join("/usr/share/doc", p.Name, rpmPath),
105
FileInfo: &files.ContentFileInfo{
106
+
MTime: internal.SourceEpoch(),
107
},
108
})
109
}
···
114
Source: repoPath,
115
Destination: rpmPath,
116
FileInfo: &files.ContentFileInfo{
117
+
MTime: internal.SourceEpoch(),
118
},
119
})
120
}
···
133
Source: path,
134
Destination: path[len(dir)+1:],
135
FileInfo: &files.ContentFileInfo{
136
+
MTime: internal.SourceEpoch(),
137
},
138
})
139
···
142
return "", fmt.Errorf("mkrpm: can't walk output directory: %w", err)
143
}
144
145
+
contents, err = files.PrepareForPackager(contents, 0o002, "rpm", true, time.Unix(0, 0))
146
+
if err != nil {
147
+
return "", fmt.Errorf("mkdeb: can't prepare for packager: %w", err)
148
+
}
149
+
150
+
for _, content := range contents {
151
+
content.FileInfo.MTime = internal.SourceEpoch()
152
+
}
153
+
154
info := nfpm.WithDefaults(&nfpm.Info{
155
Name: p.Name,
156
Version: p.Version,
···
160
Maintainer: fmt.Sprintf("%s <%s>", *internal.UserName, *internal.UserEmail),
161
Homepage: p.Homepage,
162
License: p.License,
163
+
MTime: internal.SourceEpoch(),
164
Overridables: nfpm.Overridables{
165
Contents: contents,
166
Depends: p.Depends,
···
170
},
171
})
172
173
+
info.Overridables.RPM.BuildHost = "yeet"
174
info.Overridables.RPM.Group = p.Group
175
176
if *internal.GPGKeyPassword != "" {
+2
-1
internal/mktarball/mktarball.go
+2
-1
internal/mktarball/mktarball.go
···
13
"github.com/Masterminds/semver/v3"
14
"github.com/TecharoHQ/yeet/internal"
15
"github.com/TecharoHQ/yeet/internal/pkgmeta"
16
)
17
18
func defaultFname(p pkgmeta.Package) string {
···
122
return "", fmt.Errorf("can't open root FS %s: %w", dir, err)
123
}
124
125
-
if err := tw.AddFS(root.FS()); err != nil {
126
return "", fmt.Errorf("can't copy built files to tarball: %w", err)
127
}
128
···
13
"github.com/Masterminds/semver/v3"
14
"github.com/TecharoHQ/yeet/internal"
15
"github.com/TecharoHQ/yeet/internal/pkgmeta"
16
+
"github.com/TecharoHQ/yeet/internal/vfs"
17
)
18
19
func defaultFname(p pkgmeta.Package) string {
···
123
return "", fmt.Errorf("can't open root FS %s: %w", dir, err)
124
}
125
126
+
if err := tw.AddFS(vfs.ModTimeFS{FS: root.FS(), Time: internal.SourceEpoch()}); err != nil {
127
return "", fmt.Errorf("can't copy built files to tarball: %w", err)
128
}
129
+42
internal/mktarball/mktarball_test.go
+42
internal/mktarball/mktarball_test.go
···
1
package mktarball
2
3
import (
4
+
"archive/tar"
5
+
"compress/gzip"
6
+
"io"
7
+
"os"
8
"testing"
9
10
+
"github.com/TecharoHQ/yeet/internal"
11
"github.com/TecharoHQ/yeet/internal/yeettest"
12
)
13
···
18
func TestBuildError(t *testing.T) {
19
yeettest.BuildHello(t, Build, ".0.0", false)
20
}
21
+
22
+
func TestTimestampsNotZero(t *testing.T) {
23
+
pkg := yeettest.BuildHello(t, Build, "1.0.0", true)
24
+
25
+
fin, err := os.Open(pkg)
26
+
if err != nil {
27
+
t.Fatal(err)
28
+
}
29
+
defer fin.Close()
30
+
31
+
gzr, err := gzip.NewReader(fin)
32
+
if err != nil {
33
+
t.Fatal(err)
34
+
}
35
+
defer gzr.Close()
36
+
37
+
tr := tar.NewReader(gzr)
38
+
39
+
for {
40
+
header, err := tr.Next()
41
+
switch {
42
+
case err == io.EOF:
43
+
return
44
+
case err != nil:
45
+
t.Fatal(err)
46
+
}
47
+
48
+
expect := internal.SourceEpoch()
49
+
50
+
t.Run(header.Name, func(t *testing.T) {
51
+
header := header
52
+
if !header.ModTime.Equal(expect) {
53
+
t.Errorf("file has wrong timestamp %s, wanted: %s", header.ModTime, expect)
54
+
}
55
+
})
56
+
}
57
+
}
+90
internal/vfs/modtimefs.go
+90
internal/vfs/modtimefs.go
···
···
1
+
package vfs
2
+
3
+
import (
4
+
"io/fs"
5
+
"time"
6
+
)
7
+
8
+
// ModTimeFS wraps an fs.FS and overrides all file mtimes with a fixed time.
9
+
type ModTimeFS struct {
10
+
fs.FS
11
+
Time time.Time
12
+
}
13
+
14
+
// Open overrides the FS.Open method to wrap returned files.
15
+
func (m ModTimeFS) Open(name string) (fs.File, error) {
16
+
f, err := m.FS.Open(name)
17
+
if err != nil {
18
+
return nil, err
19
+
}
20
+
return &modTimeFile{File: f, Time: m.Time}, nil
21
+
}
22
+
23
+
// ReadDir implements fs.ReadDirFS if the underlying FS supports it.
24
+
func (m ModTimeFS) ReadDir(name string) ([]fs.DirEntry, error) {
25
+
readDirFS, ok := m.FS.(fs.ReadDirFS)
26
+
if !ok {
27
+
return nil, &fs.PathError{Op: "ReadDir", Path: name, Err: fs.ErrInvalid}
28
+
}
29
+
30
+
entries, err := readDirFS.ReadDir(name)
31
+
if err != nil {
32
+
return nil, err
33
+
}
34
+
35
+
wrapped := make([]fs.DirEntry, len(entries))
36
+
for i, entry := range entries {
37
+
wrapped[i] = modTimeDirEntry{DirEntry: entry, Time: m.Time}
38
+
}
39
+
return wrapped, nil
40
+
}
41
+
42
+
// modTimeFile wraps fs.File to override Stat().ModTime().
43
+
type modTimeFile struct {
44
+
fs.File
45
+
Time time.Time
46
+
}
47
+
48
+
func (f *modTimeFile) Stat() (fs.FileInfo, error) {
49
+
info, err := f.File.Stat()
50
+
if err != nil {
51
+
return nil, err
52
+
}
53
+
return modTimeFileInfo{FileInfo: info, Time: f.Time}, nil
54
+
}
55
+
56
+
// modTimeFileInfo overrides ModTime to return a fixed time.
57
+
type modTimeFileInfo struct {
58
+
fs.FileInfo
59
+
Time time.Time
60
+
}
61
+
62
+
func (fi modTimeFileInfo) ModTime() time.Time {
63
+
return fi.Time
64
+
}
65
+
66
+
func (fi modTimeFileInfo) Uname() (string, error) {
67
+
return "root", nil
68
+
}
69
+
70
+
func (fi modTimeFileInfo) Gname() (string, error) {
71
+
return "root", nil
72
+
}
73
+
74
+
func (fi modTimeFileInfo) Sys() any {
75
+
return nil
76
+
}
77
+
78
+
// modTimeDirEntry wraps fs.DirEntry to override Info().ModTime().
79
+
type modTimeDirEntry struct {
80
+
fs.DirEntry
81
+
Time time.Time
82
+
}
83
+
84
+
func (d modTimeDirEntry) Info() (fs.FileInfo, error) {
85
+
info, err := d.DirEntry.Info()
86
+
if err != nil {
87
+
return nil, err
88
+
}
89
+
return modTimeFileInfo{FileInfo: info, Time: d.Time}, nil
90
+
}
+1
-1
package.json
+1
-1
package.json
+6
yeet.go
+6
yeet.go
···
6
// This variable is set at build time using the -X linker flag. If not set,
7
// it defaults to "devel".
8
var Version = "devel"
9
+
10
+
// BuildMethod contains the method used to build the yeet binary.
11
+
//
12
+
// This variable is set at build time using the -X linker flag. If not set,
13
+
// it defaults to "go-build".
14
+
var BuildMethod = "go-build"
+7
-7
yeetfile.js
+7
-7
yeetfile.js
···
1
const pkgs = [];
2
3
-
["amd64", "arm64"].forEach((goarch) =>
4
[deb, rpm, tarball].forEach((method) => {
5
pkgs.push(
6
method.build({
···
16
},
17
18
build: ({ bin }) => {
19
-
go.build("-o", `${bin}/yeet`, "./cmd/yeet");
20
},
21
}),
22
);
23
}),
24
);
25
-
26
-
pkgs.map((pkg) => {
27
-
gitea.uploadPackage("Techaro", "yeet", "unstable", pkg);
28
-
});
29
30
tarball.build({
31
name: "yeet-src-vendor",
···
44
},
45
46
mkFilename: ({ name, version }) => `${name}-${version}`,
47
-
});
···
1
const pkgs = [];
2
3
+
[
4
+
"amd64",
5
+
"arm64",
6
+
"ppc64le",
7
+
].forEach((goarch) =>
8
[deb, rpm, tarball].forEach((method) => {
9
pkgs.push(
10
method.build({
···
20
},
21
22
build: ({ bin }) => {
23
+
$`go build -o ${bin}/yeet -trimpath -ldflags '-buildid= -s -w -extldflags "-static" -X "github.com/TecharoHQ/yeet.Version=${git.tag()}" -X "github.com/TecharoHQ/yeet.BuildMethod=${method.name}"' ./cmd/yeet`;
24
},
25
}),
26
);
27
}),
28
);
29
30
tarball.build({
31
name: "yeet-src-vendor",
···
44
},
45
46
mkFilename: ({ name, version }) => `${name}-${version}`,
47
+
});