feat: add files() function

also add files-find-lang and files-find-doc helpers
This commit is contained in:
2025-01-24 19:23:41 +03:00
parent b5bf6ab61d
commit f6ba4a1c26
8 changed files with 523 additions and 64 deletions

View File

@ -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

View File

@ -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,
"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 {

View 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)
})
}
}