fix: add find-files #109
| @@ -11,7 +11,7 @@ | |||||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> |         <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||||
|         <text x="33.5" y="14">coverage</text> |         <text x="33.5" y="14">coverage</text> | ||||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.0%</text> |         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.4%</text> | ||||||
|         <text x="86" y="14">19.0%</text> |         <text x="86" y="14">19.4%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @@ -9,6 +9,7 @@ require ( | |||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||||
| 	github.com/PuerkitoBio/purell v1.2.0 | 	github.com/PuerkitoBio/purell v1.2.0 | ||||||
| 	github.com/alecthomas/chroma/v2 v2.9.1 | 	github.com/alecthomas/chroma/v2 v2.9.1 | ||||||
|  | 	github.com/bmatcuk/doublestar/v4 v4.8.1 | ||||||
| 	github.com/caarlos0/env v3.5.0+incompatible | 	github.com/caarlos0/env v3.5.0+incompatible | ||||||
| 	github.com/charmbracelet/bubbles v0.20.0 | 	github.com/charmbracelet/bubbles v0.20.0 | ||||||
| 	github.com/charmbracelet/bubbletea v1.2.4 | 	github.com/charmbracelet/bubbletea v1.2.4 | ||||||
| @@ -22,7 +23,6 @@ require ( | |||||||
| 	github.com/hashicorp/go-hclog v0.14.1 | 	github.com/hashicorp/go-hclog v0.14.1 | ||||||
| 	github.com/hashicorp/go-plugin v1.6.3 | 	github.com/hashicorp/go-plugin v1.6.3 | ||||||
| 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | ||||||
| 	github.com/jmoiron/sqlx v1.3.5 |  | ||||||
| 	github.com/leonelquinteros/gotext v1.7.0 | 	github.com/leonelquinteros/gotext v1.7.0 | ||||||
| 	github.com/mattn/go-isatty v0.0.20 | 	github.com/mattn/go-isatty v0.0.20 | ||||||
| 	github.com/mholt/archiver/v4 v4.0.0-alpha.8 | 	github.com/mholt/archiver/v4 v4.0.0-alpha.8 | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								go.sum
									
									
									
									
									
								
							| @@ -67,6 +67,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | |||||||
| github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | ||||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | ||||||
|  | github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= | ||||||
|  | github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= | ||||||
| github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | ||||||
| github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= | github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= | ||||||
| github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= | github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= | ||||||
| @@ -156,7 +158,6 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi | |||||||
| github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | ||||||
| github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= | github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= | ||||||
| github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= | github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= | ||||||
| github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= |  | ||||||
| github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= | ||||||
| github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | ||||||
| github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | ||||||
| @@ -251,8 +252,6 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m | |||||||
| github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= | github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= | ||||||
| github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= | github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= | ||||||
| github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= | github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= | ||||||
| github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= |  | ||||||
| github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= |  | ||||||
| github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= | ||||||
| github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||||||
| github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||||
| @@ -281,9 +280,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | ||||||
| github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | ||||||
| github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= |  | ||||||
| github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= |  | ||||||
| github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= |  | ||||||
| github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||||
| github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||||
| @@ -302,7 +298,6 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei | |||||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||||
| github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||||||
| github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= |  | ||||||
| github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | ||||||
| github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||||||
|   | |||||||
| @@ -65,6 +65,8 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{} | |||||||
| 	var chLogLevel chLog.Level | 	var chLogLevel chLog.Level | ||||||
| 	if msg == "plugin process exited" || | 	if msg == "plugin process exited" || | ||||||
| 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | ||||||
|  | 		strings.HasPrefix(msg, "[WARN] error closing client during Kill") || | ||||||
|  | 		strings.HasPrefix(msg, "[WARN] plugin failed to exit gracefully") || | ||||||
| 		strings.HasPrefix(msg, "[DEBUG] plugin") { | 		strings.HasPrefix(msg, "[DEBUG] plugin") { | ||||||
| 		chLogLevel = chLog.DebugLevel | 		chLogLevel = chLog.DebugLevel | ||||||
| 	} else { | 	} else { | ||||||
|   | |||||||
							
								
								
									
										178
									
								
								internal/shutils/helpers/files_find.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								internal/shutils/helpers/files_find.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/bmatcuk/doublestar/v4" | ||||||
|  | 	"mvdan.cc/sh/v3/interp" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func matchNamePattern(name, pattern string) bool { | ||||||
|  | 	matched, err := filepath.Match(pattern, name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return matched | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func validateDir(dirPath, commandName string) error { | ||||||
|  | 	info, err := os.Stat(dirPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("%s: %w", commandName, err) | ||||||
|  | 	} | ||||||
|  | 	if !info.IsDir() { | ||||||
|  | 		return fmt.Errorf("%s: %s is not a directory", commandName, dirPath) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func outputFiles(hc interp.HandlerContext, files []string) { | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		fmt.Fprintln(hc.Stdout, file) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func makeRelativePath(basePath, fullPath string) (string, error) { | ||||||
|  | 	relPath, err := filepath.Rel(basePath, fullPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return "./" + relPath, 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) | ||||||
|  |  | ||||||
|  | 	if err := validateDir(realPath, "files-find-lang"); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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 := makeRelativePath(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) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	outputFiles(hc, langFiles) | ||||||
|  | 	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) | ||||||
|  |  | ||||||
|  | 	if err := validateDir(docRealPath, "files-find-doc"); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var docFiles []string | ||||||
|  |  | ||||||
|  | 	entries, err := os.ReadDir(docRealPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("files-find-doc: %w", 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 fmt.Errorf("files-find-doc: %w", err) | ||||||
|  | 			} | ||||||
|  | 			if targetInfo.IsDir() { | ||||||
|  | 				err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error { | ||||||
|  | 					if subErr != nil { | ||||||
|  | 						return subErr | ||||||
|  | 					} | ||||||
|  | 					relPath, err := makeRelativePath(hc.Dir, subPath) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 					docFiles = append(docFiles, relPath) | ||||||
|  | 					return nil | ||||||
|  | 				}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return fmt.Errorf("files-find-doc: %w", err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	outputFiles(hc, docFiles) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func filesFindCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||||
|  | 	if len(args) == 0 { | ||||||
|  | 		return fmt.Errorf("find-files: at least one glob pattern is required") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var foundFiles []string | ||||||
|  |  | ||||||
|  | 	for _, globPattern := range args { | ||||||
|  | 		searchPath := path.Join(hc.Dir, globPattern) | ||||||
|  |  | ||||||
|  | 		basepath, pattern := doublestar.SplitPattern(searchPath) | ||||||
|  | 		fsys := os.DirFS(basepath) | ||||||
|  | 		matches, err := doublestar.Glob(fsys, pattern, doublestar.WithNoFollow()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			slog.Warn("find-files: invalid glob pattern", "pattern", globPattern, "error", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, match := range matches { | ||||||
|  | 			relPath, err := makeRelativePath(hc.Dir, path.Join(basepath, match)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			foundFiles = append(foundFiles, relPath) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	outputFiles(hc, foundFiles) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -24,7 +24,6 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -57,6 +56,7 @@ var Helpers = handlers.ExecFuncs{ | |||||||
| 	"install-library":      installLibraryCmd, | 	"install-library":      installLibraryCmd, | ||||||
| 	"git-version":          gitVersionCmd, | 	"git-version":          gitVersionCmd, | ||||||
|  |  | ||||||
|  | 	"files-find":      filesFindCmd, | ||||||
| 	"files-find-lang": filesFindLangCmd, | 	"files-find-lang": filesFindLangCmd, | ||||||
| 	"files-find-doc":  filesFindDocCmd, | 	"files-find-doc":  filesFindDocCmd, | ||||||
| } | } | ||||||
| @@ -65,6 +65,7 @@ var Helpers = handlers.ExecFuncs{ | |||||||
| // that don't modify any state | // that don't modify any state | ||||||
| var Restricted = handlers.ExecFuncs{ | var Restricted = handlers.ExecFuncs{ | ||||||
| 	"git-version":     gitVersionCmd, | 	"git-version":     gitVersionCmd, | ||||||
|  | 	"files-find":      filesFindCmd, | ||||||
| 	"files-find-lang": filesFindLangCmd, | 	"files-find-lang": filesFindLangCmd, | ||||||
| 	"files-find-doc":  filesFindDocCmd, | 	"files-find-doc":  filesFindDocCmd, | ||||||
| } | } | ||||||
| @@ -265,114 +266,6 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | |||||||
| 	return nil | 	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 { | func helperInstall(from, to string, perms os.FileMode) error { | ||||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -31,11 +31,17 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type symlink struct { | ||||||
|  | 	linkPath   string | ||||||
|  | 	targetPath string | ||||||
|  | } | ||||||
|  |  | ||||||
| type testCase struct { | type testCase struct { | ||||||
| 	name             string | 	name             string | ||||||
| 	dirsToCreate     []string | 	dirsToCreate     []string | ||||||
| 	filesToCreate    []string | 	filesToCreate    []string | ||||||
| 	expectedOutput   []string | 	expectedOutput   []string | ||||||
|  | 	symlinksToCreate []symlink | ||||||
| 	args             string | 	args             string | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -214,3 +220,94 @@ files-find-lang ` + tc.args | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestFindFiles(t *testing.T) { | ||||||
|  | 	tests := []testCase{ | ||||||
|  | 		{ | ||||||
|  | 			name: "With file and dir symlinks", | ||||||
|  | 			dirsToCreate: []string{ | ||||||
|  | 				"usr/share/locale/ru/LC_MESSAGES", | ||||||
|  | 				"usr/share/locale/tr/LC_MESSAGES", | ||||||
|  | 				"opt/app", | ||||||
|  | 				"opt/app/internal", | ||||||
|  | 			}, | ||||||
|  | 			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", | ||||||
|  | 				"opt/app/internal/test", | ||||||
|  | 			}, | ||||||
|  | 			symlinksToCreate: []symlink{ | ||||||
|  | 				{ | ||||||
|  | 					linkPath:   "/opt/app/etc", | ||||||
|  | 					targetPath: "/etc", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			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", | ||||||
|  | 				"./opt/app/etc", | ||||||
|  | 				"./opt/app/internal", | ||||||
|  | 				"./opt/app/internal/test", | ||||||
|  | 			}, | ||||||
|  | 			args: "\"/usr/share/locale/*/LC_MESSAGES/*.mo\" \"/opt/app/**/*\"", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			tempDir, err := os.MkdirTemp("", "test-files-find") | ||||||
|  | 			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) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, sl := range tc.symlinksToCreate { | ||||||
|  | 				linkFullPath := filepath.Join(tempDir, sl.linkPath) | ||||||
|  | 				targetFullPath := sl.targetPath | ||||||
|  |  | ||||||
|  | 				// make sure parent dir exists | ||||||
|  | 				err := os.MkdirAll(filepath.Dir(linkFullPath), 0o755) | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 				err = os.Symlink(targetFullPath, linkFullPath) | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			helpers := handlers.ExecFuncs{ | ||||||
|  | 				"files-find": filesFindCmd, | ||||||
|  | 			} | ||||||
|  | 			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 ` + 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) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user