Yeet those builds out!

Compare changes

Choose any two refs to compare.

+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
··· 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
··· 1 ## [0.2.2](https://github.com/TecharoHQ/yeet/compare/v0.2.1...v0.2.2) (2025-05-02) 2 3
··· 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
··· 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.
··· 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 + [![Packaging status](https://repology.org/badge/vertical-allrepos/yeet-js-build-tool.svg?columns=3)](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
··· 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
···
··· 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
···
··· 1 + # confyg 2 + 3 + A suitably generic form of the Go module configuration file parser. 4 + 5 + [![GoDoc](https://godoc.org/within.website/confyg?status.svg)](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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 1 + ## comment 2 + module "x" ## eol 3 + 4 + ## mid comment 5 + 6 + ## comment 2 7 + ## comment 2 line 2 8 + module "y" ## eoy 9 + 10 + ## comment 3
+8
confyg/testdata/comment.in
···
··· 1 + ## comment 2 + module "x" ## eol 3 + ## mid comment 4 + 5 + ## comment 2 6 + ## comment 2 line 2 7 + module "y" ## eoy 8 + ## comment 3
confyg/testdata/empty.golden

This is a binary file and will not be displayed.

confyg/testdata/empty.in

This is a binary file and will not be displayed.

+1
confyg/testdata/module.golden
···
··· 1 + module "abc"
+1
confyg/testdata/module.in
···
··· 1 + module "abc"
+5
confyg/testdata/replace.golden
···
··· 1 + module "abc" 2 + 3 + replace "xyz" v1.2.3 => "/tmp/z" 4 + 5 + replace "xyz" v1.3.4 => "my/xyz" v1.3.4-me
+5
confyg/testdata/replace.in
···
··· 1 + module "abc" 2 + 3 + replace "xyz" v1.2.3 => "/tmp/z" 4 + 5 + replace "xyz" v1.3.4 => "my/xyz" v1.3.4-me
+6
confyg/testdata/replace2.golden
···
··· 1 + module "abc" 2 + 3 + replace ( 4 + "xyz" v1.2.3 => "/tmp/z" 5 + "xyz" v1.3.4 => "my/xyz" v1.3.4-me 6 + )
+6
confyg/testdata/replace2.in
···
··· 1 + module "abc" 2 + 3 + replace ( 4 + "xyz" v1.2.3 => "/tmp/z" 5 + "xyz" v1.3.4 => "my/xyz" v1.3.4-me 6 + )
+7
confyg/testdata/rule1.golden
···
··· 1 + module "x" 2 + 3 + module "y" 4 + 5 + require "x" 6 + 7 + require x
+1
confyg/testdata/url.golden
···
··· 1 + url https://foo.bar
+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
··· 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
··· 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
··· 1 - # confyg 2 - 3 - A suitably generic form of the Go module configuration file parser. 4 - 5 - [![GoDoc](https://godoc.org/within.website/confyg?status.svg)](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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 1 - ## comment 2 - module "x" ## eol 3 - 4 - ## mid comment 5 - 6 - ## comment 2 7 - ## comment 2 line 2 8 - module "y" ## eoy 9 - 10 - ## comment 3
···
-8
internal/confyg/testdata/comment.in
··· 1 - ## comment 2 - module "x" ## eol 3 - ## mid comment 4 - 5 - ## comment 2 6 - ## comment 2 line 2 7 - module "y" ## eoy 8 - ## comment 3
···
internal/confyg/testdata/empty.golden

This is a binary file and will not be displayed.

internal/confyg/testdata/empty.in

This is a binary file and will not be displayed.

-1
internal/confyg/testdata/module.golden
··· 1 - module "abc"
···
-1
internal/confyg/testdata/module.in
··· 1 - module "abc"
···
-5
internal/confyg/testdata/replace.golden
··· 1 - module "abc" 2 - 3 - replace "xyz" v1.2.3 => "/tmp/z" 4 - 5 - replace "xyz" v1.3.4 => "my/xyz" v1.3.4-me
···
-5
internal/confyg/testdata/replace.in
··· 1 - module "abc" 2 - 3 - replace "xyz" v1.2.3 => "/tmp/z" 4 - 5 - replace "xyz" v1.3.4 => "my/xyz" v1.3.4-me
···
-6
internal/confyg/testdata/replace2.golden
··· 1 - module "abc" 2 - 3 - replace ( 4 - "xyz" v1.2.3 => "/tmp/z" 5 - "xyz" v1.3.4 => "my/xyz" v1.3.4-me 6 - )
···
-6
internal/confyg/testdata/replace2.in
··· 1 - module "abc" 2 - 3 - replace ( 4 - "xyz" v1.2.3 => "/tmp/z" 5 - "xyz" v1.3.4 => "my/xyz" v1.3.4-me 6 - )
···
-7
internal/confyg/testdata/rule1.golden
··· 1 - module "x" 2 - 3 - module "y" 4 - 5 - require "x" 6 - 7 - require x
···
-1
internal/confyg/testdata/url.golden
··· 1 - url https://foo.bar
···
+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
··· 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
··· 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
··· 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
··· 1 package mktarball 2 3 import ( 4 "testing" 5 6 "github.com/TecharoHQ/yeet/internal/yeettest" 7 ) 8 ··· 13 func TestBuildError(t *testing.T) { 14 yeettest.BuildHello(t, Build, ".0.0", false) 15 }
··· 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
···
··· 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 { 2 "name": "@techaro/yeet", 3 - "version": "0.2.2", 4 "description": "Yeet those actions out with JavaScript!", 5 "directories": { 6 "doc": "doc"
··· 1 { 2 "name": "@techaro/yeet", 3 + "version": "0.6.1", 4 "description": "Yeet those actions out with JavaScript!", 5 "directories": { 6 "doc": "doc"
+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"
··· 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
··· 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 + });