forked from Plemya-x/ALR
		
	feat: add files() function
also add files-find-lang and files-find-doc helpers
This commit is contained in:
		
							
								
								
									
										14
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								go.mod
									
									
									
									
									
								
							| @@ -1,8 +1,8 @@ | ||||
| module gitea.plemya-x.ru/Plemya-x/ALR | ||||
|  | ||||
| go 1.21 | ||||
| go 1.22 | ||||
|  | ||||
| toolchain go1.21.3 | ||||
| toolchain go1.23.5 | ||||
|  | ||||
| require ( | ||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||
| @@ -34,7 +34,7 @@ require ( | ||||
| 	golang.org/x/text v0.21.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	modernc.org/sqlite v1.25.0 | ||||
| 	mvdan.cc/sh/v3 v3.7.0 | ||||
| 	mvdan.cc/sh/v3 v3.10.0 | ||||
| 	plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 | ||||
| ) | ||||
|  | ||||
| @@ -110,11 +110,11 @@ require ( | ||||
| 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect | ||||
| 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect | ||||
| 	go4.org v0.0.0-20200411211856-f5505b9728dd // indirect | ||||
| 	golang.org/x/mod v0.17.0 // indirect | ||||
| 	golang.org/x/net v0.25.0 // indirect | ||||
| 	golang.org/x/mod v0.18.0 // indirect | ||||
| 	golang.org/x/net v0.26.0 // indirect | ||||
| 	golang.org/x/sync v0.10.0 // indirect | ||||
| 	golang.org/x/term v0.24.0 // indirect | ||||
| 	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect | ||||
| 	golang.org/x/term v0.25.0 // indirect | ||||
| 	golang.org/x/tools v0.22.0 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	lukechampine.com/uint128 v1.2.0 // indirect | ||||
| 	modernc.org/cc/v3 v3.40.0 // indirect | ||||
|   | ||||
							
								
								
									
										11
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								go.sum
									
									
									
									
									
								
							| @@ -97,6 +97,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t | ||||
| github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= | ||||
| github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= | ||||
| github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= | ||||
| github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| @@ -404,6 +405,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 | ||||
| golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= | ||||
| golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= | ||||
| golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= | ||||
| golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| @@ -426,6 +429,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | ||||
| golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= | ||||
| golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= | ||||
| golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= | ||||
| golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= | ||||
| golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| @@ -478,6 +483,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | ||||
| golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= | ||||
| golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= | ||||
| golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= | ||||
| golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= | ||||
| golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @@ -520,6 +527,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc | ||||
| golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= | ||||
| golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= | ||||
| golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= | ||||
| golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= | ||||
| golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| @@ -605,6 +614,8 @@ modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= | ||||
| modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= | ||||
| mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= | ||||
| mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= | ||||
| mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= | ||||
| mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= | ||||
| plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik= | ||||
| plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4= | ||||
| rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||||
|   | ||||
| @@ -177,7 +177,35 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { | ||||
| 	return func(ctx context.Context, opts ...interp.RunnerOption) error { | ||||
| 		sub := d.Runner.Subshell() | ||||
| 		for _, opt := range opts { | ||||
| 			opt(sub) | ||||
| 			err := opt(sub) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		return sub.Run(ctx, fn) | ||||
| 	}, true | ||||
| } | ||||
|  | ||||
| type PrepareFunc func(context.Context, *interp.Runner) error | ||||
|  | ||||
| func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool) { | ||||
| 	fn := d.getFunc(name) | ||||
| 	if fn == nil { | ||||
| 		return nil, false | ||||
| 	} | ||||
|  | ||||
| 	return func(ctx context.Context, opts ...interp.RunnerOption) error { | ||||
| 		sub := d.Runner.Subshell() | ||||
| 		for _, opt := range opts { | ||||
| 			err := opt(sub) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		if prepare != nil { | ||||
| 			if err := prepare(ctx, sub); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 		return sub.Run(ctx, fn) | ||||
| 	}, true | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| @@ -55,12 +56,17 @@ var Helpers = handlers.ExecFuncs{ | ||||
| 	"install-completion":   installCompletionCmd, | ||||
| 	"install-library":      installLibraryCmd, | ||||
| 	"git-version":          gitVersionCmd, | ||||
|  | ||||
| 	"files-find-lang": filesFindLangCmd, | ||||
| 	"files-find-doc":  filesFindDocCmd, | ||||
| } | ||||
|  | ||||
| // Restricted contains restricted read-only helper commands | ||||
| // that don't modify any state | ||||
| var Restricted = handlers.ExecFuncs{ | ||||
| 	"git-version":     gitVersionCmd, | ||||
| 	"files-find-lang": filesFindLangCmd, | ||||
| 	"files-find-doc":  filesFindDocCmd, | ||||
| } | ||||
|  | ||||
| func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc { | ||||
| @@ -256,6 +262,114 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	namePattern := "*.mo" | ||||
| 	if len(args) > 0 { | ||||
| 		namePattern = args[0] + ".mo" | ||||
| 	} | ||||
|  | ||||
| 	localePath := "./usr/share/locale/" | ||||
| 	realPath := path.Join(hc.Dir, localePath) | ||||
|  | ||||
| 	info, err := os.Stat(realPath) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("files-find-lang: %w", err) | ||||
| 	} | ||||
| 	if !info.IsDir() { | ||||
| 		return fmt.Errorf("files-find-lang: %s is not a directory", localePath) | ||||
| 	} | ||||
|  | ||||
| 	var langFiles []string | ||||
| 	err = filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error { | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if !info.IsDir() && matchNamePattern(info.Name(), namePattern) { | ||||
| 			relPath, relErr := filepath.Rel(hc.Dir, p) | ||||
| 			if relErr != nil { | ||||
| 				return relErr | ||||
| 			} | ||||
| 			langFiles = append(langFiles, "./"+relPath) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("files-find-lang: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, file := range langFiles { | ||||
| 		fmt.Fprintln(hc.Stdout, file) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	namePattern := "*" | ||||
| 	if len(args) > 0 { | ||||
| 		namePattern = args[0] | ||||
| 	} | ||||
|  | ||||
| 	docPath := "./usr/share/doc/" | ||||
| 	docRealPath := path.Join(hc.Dir, docPath) | ||||
|  | ||||
| 	info, err := os.Stat(docRealPath) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("files-find-doc: %w", err) | ||||
| 	} | ||||
| 	if !info.IsDir() { | ||||
| 		return fmt.Errorf("files-find-doc: %s is not a directory", docPath) | ||||
| 	} | ||||
|  | ||||
| 	var docFiles []string | ||||
|  | ||||
| 	entries, err := os.ReadDir(docRealPath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, entry := range entries { | ||||
| 		if matchNamePattern(entry.Name(), namePattern) { | ||||
| 			targetPath := filepath.Join(docRealPath, entry.Name()) | ||||
| 			targetInfo, err := os.Stat(targetPath) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			if targetInfo.IsDir() { | ||||
| 				err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error { | ||||
| 					relPath, err := filepath.Rel(hc.Dir, subPath) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 					docFiles = append(docFiles, "./"+relPath) | ||||
| 					return nil | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("files-find-doc: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, file := range docFiles { | ||||
| 		fmt.Fprintln(hc.Stdout, file) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func matchNamePattern(name, pattern string) bool { | ||||
| 	matched, err := filepath.Match(pattern, name) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return matched | ||||
| } | ||||
|  | ||||
| func helperInstall(from, to string, perms os.FileMode) error { | ||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||
| 	if err != nil { | ||||
|   | ||||
							
								
								
									
										216
									
								
								internal/shutils/helpers/helpers_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								internal/shutils/helpers/helpers_internal_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 Евгений Храмов | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package helpers | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||
| ) | ||||
|  | ||||
| type testCase struct { | ||||
| 	name           string | ||||
| 	dirsToCreate   []string | ||||
| 	filesToCreate  []string | ||||
| 	expectedOutput []string | ||||
| 	args           string | ||||
| } | ||||
|  | ||||
| func TestFindFilesDoc(t *testing.T) { | ||||
| 	tests := []testCase{ | ||||
| 		{ | ||||
| 			name: "All dirs", | ||||
| 			dirsToCreate: []string{ | ||||
| 				"usr/share/doc/yandex-browser-stable/subdir", | ||||
| 				"usr/share/doc/firefox", | ||||
| 			}, | ||||
| 			filesToCreate: []string{ | ||||
| 				"usr/share/doc/yandex-browser-stable/README.md", | ||||
| 				"usr/share/doc/yandex-browser-stable/subdir/nested-file.txt", | ||||
| 				"usr/share/doc/firefox/README.md", | ||||
| 			}, | ||||
| 			expectedOutput: []string{ | ||||
| 				"./usr/share/doc/yandex-browser-stable", | ||||
| 				"./usr/share/doc/yandex-browser-stable/README.md", | ||||
| 				"./usr/share/doc/yandex-browser-stable/subdir", | ||||
| 				"./usr/share/doc/yandex-browser-stable/subdir/nested-file.txt", | ||||
| 				"./usr/share/doc/firefox", | ||||
| 				"./usr/share/doc/firefox/README.md", | ||||
| 			}, | ||||
| 			args: "", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Only selected dir", | ||||
| 			dirsToCreate: []string{ | ||||
| 				"usr/share/doc/yandex-browser-stable/subdir", | ||||
| 				"usr/share/doc/firefox", | ||||
| 				"usr/share/doc/foo/yandex-browser-stable", | ||||
| 			}, | ||||
| 			filesToCreate: []string{ | ||||
| 				"usr/share/doc/yandex-browser-stable/README.md", | ||||
| 				"usr/share/doc/yandex-browser-stable/subdir/nested-file.txt", | ||||
| 				"usr/share/doc/firefox/README.md", | ||||
| 				"usr/share/doc/firefox/yandex-browser-stable", | ||||
| 				"usr/share/doc/foo/yandex-browser-stable/README.md", | ||||
| 			}, | ||||
| 			expectedOutput: []string{ | ||||
| 				"./usr/share/doc/yandex-browser-stable", | ||||
| 				"./usr/share/doc/yandex-browser-stable/README.md", | ||||
| 				"./usr/share/doc/yandex-browser-stable/subdir", | ||||
| 				"./usr/share/doc/yandex-browser-stable/subdir/nested-file.txt", | ||||
| 			}, | ||||
| 			args: "yandex-browser-stable", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			tempDir, err := os.MkdirTemp("", "test-files-find-doc") | ||||
| 			assert.NoError(t, err) | ||||
| 			defer os.RemoveAll(tempDir) | ||||
|  | ||||
| 			for _, dir := range tc.dirsToCreate { | ||||
| 				dirPath := filepath.Join(tempDir, dir) | ||||
| 				err := os.MkdirAll(dirPath, 0o755) | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
|  | ||||
| 			for _, file := range tc.filesToCreate { | ||||
| 				filePath := filepath.Join(tempDir, file) | ||||
| 				err := os.WriteFile(filePath, []byte("test content"), 0o644) | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
|  | ||||
| 			helpers := handlers.ExecFuncs{ | ||||
| 				"files-find-doc": filesFindDocCmd, | ||||
| 			} | ||||
| 			buf := &bytes.Buffer{} | ||||
| 			runner, err := interp.New( | ||||
| 				interp.Dir(tempDir), | ||||
| 				interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 				interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))), | ||||
| 			) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			scriptContent := ` | ||||
| shopt -s globstar | ||||
| files-find-doc ` + tc.args | ||||
|  | ||||
| 			script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "") | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = runner.Run(context.Background(), script) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			contents := strings.Fields(strings.TrimSpace(buf.String())) | ||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFindLang(t *testing.T) { | ||||
| 	tests := []testCase{ | ||||
| 		{ | ||||
| 			name: "All dirs", | ||||
| 			dirsToCreate: []string{ | ||||
| 				"usr/share/locale/ru/LC_MESSAGES", | ||||
| 				"usr/share/locale/tr/LC_MESSAGES", | ||||
| 			}, | ||||
| 			filesToCreate: []string{ | ||||
| 				"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo", | ||||
| 				"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo", | ||||
| 				"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo", | ||||
| 			}, | ||||
| 			expectedOutput: []string{ | ||||
| 				"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo", | ||||
| 				"./usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo", | ||||
| 				"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo", | ||||
| 			}, | ||||
| 			args: "", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "All dirs", | ||||
| 			dirsToCreate: []string{ | ||||
| 				"usr/share/locale/ru/LC_MESSAGES", | ||||
| 				"usr/share/locale/tr/LC_MESSAGES", | ||||
| 			}, | ||||
| 			filesToCreate: []string{ | ||||
| 				"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo", | ||||
| 				"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo", | ||||
| 				"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo", | ||||
| 			}, | ||||
| 			expectedOutput: []string{ | ||||
| 				"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo", | ||||
| 				"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo", | ||||
| 			}, | ||||
| 			args: "yandex-disk", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range tests { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			tempDir, err := os.MkdirTemp("", "test-files-find-lang") | ||||
| 			assert.NoError(t, err) | ||||
| 			defer os.RemoveAll(tempDir) | ||||
|  | ||||
| 			for _, dir := range tc.dirsToCreate { | ||||
| 				dirPath := filepath.Join(tempDir, dir) | ||||
| 				err := os.MkdirAll(dirPath, 0o755) | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
|  | ||||
| 			for _, file := range tc.filesToCreate { | ||||
| 				filePath := filepath.Join(tempDir, file) | ||||
| 				err := os.WriteFile(filePath, []byte("test content"), 0o644) | ||||
| 				assert.NoError(t, err) | ||||
| 			} | ||||
|  | ||||
| 			helpers := handlers.ExecFuncs{ | ||||
| 				"files-find-lang": filesFindLangCmd, | ||||
| 			} | ||||
| 			buf := &bytes.Buffer{} | ||||
| 			runner, err := interp.New( | ||||
| 				interp.Dir(tempDir), | ||||
| 				interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 				interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))), | ||||
| 			) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			scriptContent := ` | ||||
| shopt -s globstar | ||||
| files-find-lang ` + tc.args | ||||
|  | ||||
| 			script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "") | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			err = runner.Run(context.Background(), script) | ||||
| 			assert.NoError(t, err) | ||||
|  | ||||
| 			contents := strings.Fields(strings.TrimSpace(buf.String())) | ||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -251,7 +251,7 @@ msgstr "" | ||||
| msgid "Downloading source" | ||||
| msgstr "" | ||||
|  | ||||
| #: internal/logger/log.go:44 | ||||
| #: internal/logger/log.go:47 | ||||
| msgid "ERROR" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -323,35 +323,43 @@ msgstr "" | ||||
| msgid "Installing dependencies" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:435 | ||||
| #: pkg/build/build.go:439 | ||||
| msgid "Executing version()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:460 | ||||
| #: pkg/build/build.go:459 | ||||
| msgid "Updating version" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:464 | ||||
| msgid "Executing prepare()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:470 | ||||
| #: pkg/build/build.go:474 | ||||
| msgid "Executing build()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:482 | ||||
| #: pkg/build/build.go:486 | ||||
| msgid "Executing package()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:557 | ||||
| #: pkg/build/build.go:524 | ||||
| msgid "Executing files()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:601 | ||||
| msgid "AutoProv is not implemented for this package format, so it's skiped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:568 | ||||
| #: pkg/build/build.go:612 | ||||
| msgid "AutoReq is not implemented for this package format, so it's skiped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:653 | ||||
| #: pkg/build/build.go:719 | ||||
| msgid "Would you like to remove the build dependencies?" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:759 | ||||
| #: pkg/build/build.go:825 | ||||
| msgid "The checksums array must be the same length as sources" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -437,10 +445,14 @@ msgstr "" | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:47 | ||||
| #: upgrade.go:46 | ||||
| msgid "Upgrade all installed packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:83 | ||||
| #: upgrade.go:82 | ||||
| msgid "Error checking for updates" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:93 | ||||
| msgid "There is nothing to do." | ||||
| msgstr "" | ||||
|   | ||||
| @@ -259,7 +259,7 @@ msgstr "" | ||||
| msgid "Downloading source" | ||||
| msgstr "Скачивание источника" | ||||
|  | ||||
| #: internal/logger/log.go:44 | ||||
| #: internal/logger/log.go:47 | ||||
| msgid "ERROR" | ||||
| msgstr "ОШИБКА" | ||||
|  | ||||
| @@ -331,35 +331,43 @@ msgstr "" | ||||
| msgid "Installing dependencies" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:435 | ||||
| #: pkg/build/build.go:439 | ||||
| msgid "Executing version()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:460 | ||||
| #: pkg/build/build.go:459 | ||||
| msgid "Updating version" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:464 | ||||
| msgid "Executing prepare()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:470 | ||||
| #: pkg/build/build.go:474 | ||||
| msgid "Executing build()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:482 | ||||
| #: pkg/build/build.go:486 | ||||
| msgid "Executing package()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:557 | ||||
| #: pkg/build/build.go:524 | ||||
| msgid "Executing files()" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:601 | ||||
| msgid "AutoProv is not implemented for this package format, so it's skiped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:568 | ||||
| #: pkg/build/build.go:612 | ||||
| msgid "AutoReq is not implemented for this package format, so it's skiped" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:653 | ||||
| #: pkg/build/build.go:719 | ||||
| msgid "Would you like to remove the build dependencies?" | ||||
| msgstr "" | ||||
|  | ||||
| #: pkg/build/build.go:759 | ||||
| #: pkg/build/build.go:825 | ||||
| msgid "The checksums array must be the same length as sources" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -446,10 +454,14 @@ msgstr "" | ||||
| msgid "Pull all repositories that have changed" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:47 | ||||
| #: upgrade.go:46 | ||||
| msgid "Upgrade all installed packages" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:83 | ||||
| #: upgrade.go:82 | ||||
| msgid "Error checking for updates" | ||||
| msgstr "" | ||||
|  | ||||
| #: upgrade.go:93 | ||||
| msgid "There is nothing to do." | ||||
| msgstr "" | ||||
|   | ||||
| @@ -156,7 +156,7 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции | ||||
| 	funcOut, err := executeFunctions(ctx, dec, dirs, vars) // Выполняем специальные функции | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| @@ -165,7 +165,7 @@ func BuildPackage(ctx context.Context, opts types.BuildOpts) ([]string, []string | ||||
|  | ||||
| 	pkgFormat := getPkgFormat(opts.Manager) // Получаем формат пакета | ||||
|  | ||||
| 	pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, info, append(repoDeps, builtNames...)) // Собираем метаданные пакета | ||||
| 	pkgInfo, err := buildPkgMetadata(ctx, vars, dirs, pkgFormat, info, append(repoDeps, builtNames...), funcOut.Contents) // Собираем метаданные пакета | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| @@ -428,40 +428,44 @@ func buildALRDeps(ctx context.Context, opts types.BuildOpts, vars *types.BuildVa | ||||
| 	return builtPaths, builtNames, repoDeps, nil | ||||
| } | ||||
|  | ||||
| type FunctionsOutput struct { | ||||
| 	Contents *[]string | ||||
| } | ||||
|  | ||||
| // Функция executeFunctions выполняет специальные функции ALR, такие как version(), prepare() и т.д. | ||||
| func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (err error) { | ||||
| func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Directories, vars *types.BuildVars) (*FunctionsOutput, error) { | ||||
| 	version, ok := dec.GetFunc("version") | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing version()")) | ||||
|  | ||||
| 		buf := &bytes.Buffer{} | ||||
|  | ||||
| 		err = version( | ||||
| 		err := version( | ||||
| 			ctx, | ||||
| 			interp.Dir(dirs.SrcDir), | ||||
| 			interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		newVer := strings.TrimSpace(buf.String()) | ||||
| 		err = setVersion(ctx, dec.Runner, newVer) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		vars.Version = newVer | ||||
|  | ||||
| 		slog.Info("Updating version", "new", newVer) | ||||
| 		slog.Info(gotext.Get("Updating version"), "new", newVer) | ||||
| 	} | ||||
|  | ||||
| 	prepare, ok := dec.GetFunc("prepare") | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing prepare()")) | ||||
|  | ||||
| 		err = prepare(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		err := prepare(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -469,9 +473,9 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing build()")) | ||||
|  | ||||
| 		err = build(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		err := build(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -480,9 +484,9 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire | ||||
| 		packageFn, ok := dec.GetFunc("package") | ||||
| 		if ok { | ||||
| 			slog.Info(gotext.Get("Executing package()")) | ||||
| 			err = packageFn(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 			err := packageFn(ctx, interp.Dir(dirs.SrcDir)) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -502,11 +506,51 @@ func executeFunctions(ctx context.Context, dec *decoder.Decoder, dirs types.Dire | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| 	output := &FunctionsOutput{} | ||||
|  | ||||
| 	files, ok := dec.GetFuncP("files", func(ctx context.Context, s *interp.Runner) error { | ||||
| 		// It should be done via interp.RunnerOption, | ||||
| 		// but due to the issues below, it cannot be done. | ||||
| 		// - https://github.com/mvdan/sh/issues/962 | ||||
| 		// - https://github.com/mvdan/sh/issues/1125 | ||||
| 		script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "") | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return s.Run(ctx, script) | ||||
| 	}) | ||||
|  | ||||
| 	if ok { | ||||
| 		slog.Info(gotext.Get("Executing files()")) | ||||
|  | ||||
| 		buf := &bytes.Buffer{} | ||||
|  | ||||
| 		err := files( | ||||
| 			ctx, | ||||
| 			interp.Dir(dirs.PkgDir), | ||||
| 			interp.StdIO(os.Stdin, buf, os.Stderr), | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		contents := strings.Fields(strings.TrimSpace(buf.String())) | ||||
| 		output.Contents = &contents | ||||
| 	} | ||||
|  | ||||
| 	return output, nil | ||||
| } | ||||
|  | ||||
| // Функция buildPkgMetadata создает метаданные для пакета, который будет собран. | ||||
| func buildPkgMetadata(ctx context.Context, vars *types.BuildVars, dirs types.Directories, pkgFormat string, info *distro.OSRelease, deps []string) (*nfpm.Info, error) { | ||||
| func buildPkgMetadata( | ||||
| 	ctx context.Context, | ||||
| 	vars *types.BuildVars, | ||||
| 	dirs types.Directories, | ||||
| 	pkgFormat string, | ||||
| 	info *distro.OSRelease, | ||||
| 	deps []string, | ||||
| 	preferedContents *[]string, | ||||
| ) (*nfpm.Info, error) { | ||||
| 	pkgInfo := getBasePkgInfo(vars) | ||||
| 	pkgInfo.Description = vars.Description | ||||
| 	pkgInfo.Platform = "linux" | ||||
| @@ -541,7 +585,7 @@ func buildPkgMetadata(ctx context.Context, vars *types.BuildVars, dirs types.Dir | ||||
| 		pkgInfo.Arch = "all" | ||||
| 	} | ||||
|  | ||||
| 	contents, err := buildContents(vars, dirs) | ||||
| 	contents, err := buildContents(vars, dirs, preferedContents) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -574,22 +618,28 @@ func buildPkgMetadata(ctx context.Context, vars *types.BuildVars, dirs types.Dir | ||||
|  | ||||
| // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | ||||
| // которые будут включены в конечный пакет. | ||||
| func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Content, error) { | ||||
| func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { | ||||
| 	contents := []*files.Content{} | ||||
| 	err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error { | ||||
| 		trimmed := strings.TrimPrefix(path, dirs.PkgDir) | ||||
|  | ||||
| 	processPath := func(path, trimmed string, prefered bool) error { | ||||
| 		fi, err := os.Lstat(path) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if fi.IsDir() { | ||||
| 			f, err := os.Open(path) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			defer f.Close() | ||||
|  | ||||
| 			// Если директория пустая, пропускаем её | ||||
| 			if !prefered { | ||||
| 				_, err = f.Readdirnames(1) | ||||
| 				if err != io.EOF { | ||||
| 					return nil | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			contents = append(contents, &files.Content{ | ||||
| 				Source:      path, | ||||
| @@ -599,16 +649,14 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont | ||||
| 					MTime: fi.ModTime(), | ||||
| 				}, | ||||
| 			}) | ||||
|  | ||||
| 			return f.Close() | ||||
| 			return nil | ||||
| 		} | ||||
| 		// Если файл является символической ссылкой, прорабатываем это | ||||
|  | ||||
| 		if fi.Mode()&os.ModeSymlink != 0 { | ||||
| 			link, err := os.Readlink(path) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			// Удаляем pkgdir из пути символической ссылки | ||||
| 			link = strings.TrimPrefix(link, dirs.PkgDir) | ||||
|  | ||||
| 			contents = append(contents, &files.Content{ | ||||
| @@ -620,10 +668,9 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont | ||||
| 					Mode:  fi.Mode(), | ||||
| 				}, | ||||
| 			}) | ||||
|  | ||||
| 			return nil | ||||
| 		} | ||||
| 		// Обрабатываем обычные файлы | ||||
|  | ||||
| 		fileContent := &files.Content{ | ||||
| 			Source:      path, | ||||
| 			Destination: trimmed, | ||||
| @@ -634,16 +681,35 @@ func buildContents(vars *types.BuildVars, dirs types.Directories) ([]*files.Cont | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		// Если файл должен быть сохранен, установите его тип как config|noreplace | ||||
| 		if slices.Contains(vars.Backup, trimmed) { | ||||
| 			fileContent.Type = "config|noreplace" | ||||
| 		} | ||||
|  | ||||
| 		contents = append(contents, fileContent) | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if preferedContents != nil { | ||||
| 		for _, trimmed := range *preferedContents { | ||||
| 			path := filepath.Join(dirs.PkgDir, trimmed) | ||||
| 			if err := processPath(path, trimmed, true); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 	} else { | ||||
| 		err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error { | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			trimmed := strings.TrimPrefix(path, dirs.PkgDir) | ||||
| 			return processPath(path, trimmed, false) | ||||
| 		}) | ||||
| 	return contents, err | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return contents, nil | ||||
| } | ||||
|  | ||||
| // Функция removeBuildDeps спрашивает у пользователя, хочет ли он удалить зависимости, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user