diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg index a1f8f90..ce89bb7 100644 --- a/assets/coverage-badge.svg +++ b/assets/coverage-badge.svg @@ -11,7 +11,7 @@ coverage coverage - 16.9% - 16.9% + 16.7% + 16.7% diff --git a/internal/build/build.go b/internal/build/build.go index 301a362..aa18dc2 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -27,14 +27,13 @@ import ( "log/slog" "github.com/leonelquinteros/gotext" - "mvdan.cc/sh/v3/syntax" - "mvdan.cc/sh/v3/syntax/typedjson" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/distro" "gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" + "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" ) @@ -133,47 +132,6 @@ type RepositoryProvider interface { // ================================================ -type ScriptFile struct { - File *syntax.File - Path string -} - -func (s *ScriptFile) GobEncode() ([]byte, error) { - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - if err := enc.Encode(s.Path); err != nil { - return nil, err - } - var fileBuf bytes.Buffer - if err := typedjson.Encode(&fileBuf, s.File); err != nil { - return nil, err - } - fileData := fileBuf.Bytes() - if err := enc.Encode(fileData); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func (s *ScriptFile) GobDecode(data []byte) error { - buf := bytes.NewBuffer(data) - dec := gob.NewDecoder(buf) - if err := dec.Decode(&s.Path); err != nil { - return err - } - var fileData []byte - if err := dec.Decode(&fileData); err != nil { - return err - } - fileReader := bytes.NewReader(fileData) - file, err := typedjson.Decode(fileReader) - if err != nil { - return err - } - s.File = file.(*syntax.File) - return nil -} - type BuiltDep struct { Name string Path string @@ -219,8 +177,8 @@ type ScriptResolverExecutor interface { } type ScriptExecutor interface { - ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) - ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) + ReadScript(ctx context.Context, scriptPath string) (*alrsh.ALRSh, error) + ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh) (string, []*types.BuildVars, error) PrepareDirs( ctx context.Context, input *BuildInput, @@ -229,7 +187,7 @@ type ScriptExecutor interface { ExecuteSecondPass( ctx context.Context, input *BuildInput, - sf *ScriptFile, + sf *alrsh.ALRSh, varsOfPackages []*types.BuildVars, repoDeps []string, builtDeps []*BuiltDep, @@ -242,7 +200,7 @@ type CacheExecutor interface { } type ScriptViewerExecutor interface { - ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error + ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh, basePkg string) error } type CheckerExecutor interface { diff --git a/internal/build/safe_script_executor.go b/internal/build/safe_script_executor.go index 8c18b01..461bf81 100644 --- a/internal/build/safe_script_executor.go +++ b/internal/build/safe_script_executor.go @@ -28,6 +28,7 @@ import ( "github.com/hashicorp/go-plugin" "gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" + "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" ) @@ -50,13 +51,13 @@ type ScriptExecutorRPCServer struct { // ReadScript // -func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { - var resp *ScriptFile +func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ALRSh, error) { + var resp *alrsh.ALRSh err := s.client.Call("Plugin.ReadScript", scriptPath, &resp) return resp, err } -func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error { +func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ALRSh) error { file, err := s.Impl.ReadScript(context.Background(), scriptPath) if err != nil { return err @@ -72,7 +73,7 @@ func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile type ExecuteFirstPassArgs struct { Input *BuildInput - Sf *ScriptFile + Sf *alrsh.ALRSh } type ExecuteFirstPassResp struct { @@ -80,7 +81,7 @@ type ExecuteFirstPassResp struct { VarsOfPackages []*types.BuildVars } -func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { +func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh) (string, []*types.BuildVars, error) { var resp *ExecuteFirstPassResp err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{ Input: input, @@ -148,7 +149,7 @@ func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *stru type ExecuteSecondPassArgs struct { Input *BuildInput - Sf *ScriptFile + Sf *alrsh.ALRSh VarsOfPackages []*types.BuildVars RepoDeps []string BuiltDeps []*BuiltDep @@ -158,7 +159,7 @@ type ExecuteSecondPassArgs struct { func (s *ScriptExecutorRPC) ExecuteSecondPass( ctx context.Context, input *BuildInput, - sf *ScriptFile, + sf *alrsh.ALRSh, varsOfPackages []*types.BuildVars, repoDeps []string, builtDeps []*BuiltDep, diff --git a/internal/build/script_executor.go b/internal/build/script_executor.go index eabce5c..01020a9 100644 --- a/internal/build/script_executor.go +++ b/internal/build/script_executor.go @@ -19,7 +19,6 @@ package build import ( "bytes" "context" - "errors" "fmt" "log/slog" "os" @@ -37,10 +36,10 @@ import ( "mvdan.cc/sh/v3/syntax" finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps" - "gitea.plemya-x.ru/Plemya-x/ALR/internal/distro" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" + "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" ) @@ -54,102 +53,12 @@ func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor { } } -func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { - fl, err := readScript(scriptPath) - if err != nil { - return nil, err - } - return &ScriptFile{ - Path: scriptPath, - File: fl, - }, nil +func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ALRSh, error) { + return alrsh.ReadFromLocal(scriptPath) } -func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { - varsOfPackages := []*types.BuildVars{} - - scriptDir := filepath.Dir(sf.Path) - env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir}) - - runner, err := interp.New( - interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение - interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод - interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение - interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий - interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов - interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов - interp.Dir(scriptDir), - ) - if err != nil { - return "", nil, err - } - - err = runner.Run(ctx, sf.File) // Запускаем скрипт - if err != nil { - return "", nil, err - } - - dec := decoder.New(input.info, runner) // Создаём новый декодер - - type packages struct { - BasePkgName string `sh:"basepkg_name"` - Names []string `sh:"name"` - } - - var pkgs packages - err = dec.DecodeVars(&pkgs) - if err != nil { - return "", nil, err - } - - if len(pkgs.Names) == 0 { - return "", nil, errors.New("package name is missing") - } - - var vars types.BuildVars - - if len(pkgs.Names) == 1 { - err = dec.DecodeVars(&vars) // Декодируем переменные - if err != nil { - return "", nil, err - } - varsOfPackages = append(varsOfPackages, &vars) - - return vars.Name, varsOfPackages, nil - } - - var pkgNames []string - - if len(input.packages) != 0 { - pkgNames = input.packages - } else { - pkgNames = pkgs.Names - } - - for _, pkgName := range pkgNames { - var preVars types.BuildVarsPre - funcName := fmt.Sprintf("meta_%s", pkgName) - meta, ok := dec.GetFuncWithSubshell(funcName) - if !ok { - return "", nil, fmt.Errorf("func %s is missing", funcName) - } - r, err := meta(ctx) - if err != nil { - return "", nil, err - } - d := decoder.New(&distro.OSRelease{}, r) - err = d.DecodeVars(&preVars) - if err != nil { - return "", nil, err - } - vars := preVars.ToBuildVars() - vars.Name = pkgName - vars.Base = pkgs.BasePkgName - - varsOfPackages = append(varsOfPackages, &vars) - } - - return pkgs.BasePkgName, varsOfPackages, nil +func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ALRSh) (string, []*types.BuildVars, error) { + return sf.ParseBuildVars(ctx, input.info, input.packages) } func (e *LocalScriptExecutor) PrepareDirs( @@ -177,13 +86,13 @@ func (e *LocalScriptExecutor) PrepareDirs( func (e *LocalScriptExecutor) ExecuteSecondPass( ctx context.Context, input *BuildInput, - sf *ScriptFile, + sf *alrsh.ALRSh, varsOfPackages []*types.BuildVars, repoDeps []string, builtDeps []*BuiltDep, basePkg string, ) ([]*BuiltDep, error) { - dirs, err := getDirs(e.cfg, sf.Path, basePkg) + dirs, err := getDirs(e.cfg, sf.Path(), basePkg) if err != nil { return nil, err } @@ -201,7 +110,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass( return nil, err } - err = runner.Run(ctx, sf.File) + err = runner.Run(ctx, sf.File()) if err != nil { return nil, err } diff --git a/internal/build/script_view.go b/internal/build/script_view.go index 2da9250..52f47c3 100644 --- a/internal/build/script_view.go +++ b/internal/build/script_view.go @@ -20,6 +20,7 @@ import ( "context" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" + "gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" ) type ScriptViewerConfig interface { @@ -33,12 +34,12 @@ type ScriptViewer struct { func (s *ScriptViewer) ViewScript( ctx context.Context, input *BuildInput, - sf *ScriptFile, + a *alrsh.ALRSh, basePkg string, ) error { return cliutils.PromptViewScript( ctx, - sf.Path, + a.Path(), basePkg, s.config.PagerStyle(), input.opts.Interactive, diff --git a/internal/build/utils.go b/internal/build/utils.go index aa30686..c1be2b7 100644 --- a/internal/build/utils.go +++ b/internal/build/utils.go @@ -33,7 +33,6 @@ import ( _ "github.com/goreleaser/nfpm/v2/arch" _ "github.com/goreleaser/nfpm/v2/deb" _ "github.com/goreleaser/nfpm/v2/rpm" - "mvdan.cc/sh/v3/syntax" "github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2/files" @@ -45,22 +44,6 @@ import ( "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" ) -// Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash -func readScript(script string) (*syntax.File, error) { - fl, err := os.Open(script) // Открываем файл скрипта - if err != nil { - return nil, err - } - defer fl.Close() // Закрываем файл после выполнения - - file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора - if err != nil { - return nil, err - } - - return file, nil // Возвращаем синтаксическое дерево -} - // Функция prepareDirs подготавливает директории для сборки. func prepareDirs(dirs types.Directories) error { err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует diff --git a/internal/translations/default.pot b/internal/translations/default.pot index 92082a1..10fe091 100644 --- a/internal/translations/default.pot +++ b/internal/translations/default.pot @@ -178,19 +178,19 @@ msgstr "" msgid "Error removing packages" msgstr "" -#: internal/build/build.go:417 +#: internal/build/build.go:375 msgid "Building package" msgstr "" -#: internal/build/build.go:446 +#: internal/build/build.go:404 msgid "The checksums array must be the same length as sources" msgstr "" -#: internal/build/build.go:488 +#: internal/build/build.go:446 msgid "Downloading sources" msgstr "" -#: internal/build/build.go:580 +#: internal/build/build.go:538 msgid "Installing dependencies" msgstr "" @@ -224,19 +224,19 @@ msgstr "" msgid "AutoReq is not implemented for this package format, so it's skipped" msgstr "" -#: internal/build/script_executor.go:236 +#: internal/build/script_executor.go:145 msgid "Building package metadata" msgstr "" -#: internal/build/script_executor.go:366 +#: internal/build/script_executor.go:275 msgid "Executing prepare()" msgstr "" -#: internal/build/script_executor.go:375 +#: internal/build/script_executor.go:284 msgid "Executing build()" msgstr "" -#: internal/build/script_executor.go:404 internal/build/script_executor.go:424 +#: internal/build/script_executor.go:313 internal/build/script_executor.go:333 msgid "Executing %s()" msgstr "" diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po index dd35c8c..13fc296 100644 --- a/internal/translations/po/ru/default.po +++ b/internal/translations/po/ru/default.po @@ -185,19 +185,19 @@ msgstr "Для команды remove ожидался хотя бы 1 аргум msgid "Error removing packages" msgstr "Ошибка при удалении пакетов" -#: internal/build/build.go:417 +#: internal/build/build.go:375 msgid "Building package" msgstr "Сборка пакета" -#: internal/build/build.go:446 +#: internal/build/build.go:404 msgid "The checksums array must be the same length as sources" msgstr "Массив контрольных сумм должен быть той же длины, что и источники" -#: internal/build/build.go:488 +#: internal/build/build.go:446 msgid "Downloading sources" msgstr "Скачивание источников" -#: internal/build/build.go:580 +#: internal/build/build.go:538 msgid "Installing dependencies" msgstr "Установка зависимостей" @@ -235,19 +235,19 @@ msgid "AutoReq is not implemented for this package format, so it's skipped" msgstr "" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" -#: internal/build/script_executor.go:236 +#: internal/build/script_executor.go:145 msgid "Building package metadata" msgstr "Сборка метаданных пакета" -#: internal/build/script_executor.go:366 +#: internal/build/script_executor.go:275 msgid "Executing prepare()" msgstr "Выполнение prepare()" -#: internal/build/script_executor.go:375 +#: internal/build/script_executor.go:284 msgid "Executing build()" msgstr "Выполнение build()" -#: internal/build/script_executor.go:404 internal/build/script_executor.go:424 +#: internal/build/script_executor.go:313 internal/build/script_executor.go:333 msgid "Executing %s()" msgstr "Выполнение %s()" diff --git a/pkg/alrsh/alrsh.go b/pkg/alrsh/alrsh.go new file mode 100644 index 0000000..f4a8ff2 --- /dev/null +++ b/pkg/alrsh/alrsh.go @@ -0,0 +1,168 @@ +// 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 . + +package alrsh + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + + "mvdan.cc/sh/v3/expand" + "mvdan.cc/sh/v3/interp" + "mvdan.cc/sh/v3/syntax" + + "gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/distro" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" + "gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" + "gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" +) + +type ALRSh struct { + file *syntax.File + path string +} + +func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string { + env := os.Environ() + + env = append( + env, + "DISTRO_NAME="+info.Name, + "DISTRO_PRETTY_NAME="+info.PrettyName, + "DISTRO_ID="+info.ID, + "DISTRO_VERSION_ID="+info.VersionID, + "DISTRO_ID_LIKE="+strings.Join(info.Like, " "), + "ARCH="+cpu.Arch(), + "NCPU="+strconv.Itoa(runtime.NumCPU()), + ) + + if dirs.ScriptDir != "" { + env = append(env, "scriptdir="+dirs.ScriptDir) + } + + if dirs.PkgDir != "" { + env = append(env, "pkgdir="+dirs.PkgDir) + } + + if dirs.SrcDir != "" { + env = append(env, "srcdir="+dirs.SrcDir) + } + + return env +} + +func (s *ALRSh) ParseBuildVars(ctx context.Context, info *distro.OSRelease, packages []string) (string, []*types.BuildVars, error) { + varsOfPackages := []*types.BuildVars{} + + scriptDir := filepath.Dir(s.path) + env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir}) + + runner, err := interp.New( + interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение + interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод + interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение + interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), // Ограничиваем чтение директорий + interp.StatHandler(handlers.RestrictedStat(scriptDir)), // Ограничиваем доступ к статистике файлов + interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), // Ограничиваем открытие файлов + interp.Dir(scriptDir), + ) + if err != nil { + return "", nil, err + } + + err = runner.Run(ctx, s.file) // Запускаем скрипт + if err != nil { + return "", nil, err + } + + dec := decoder.New(info, runner) // Создаём новый декодер + + type Packages struct { + BasePkgName string `sh:"basepkg_name"` + Names []string `sh:"name"` + } + + var pkgs Packages + err = dec.DecodeVars(&pkgs) + if err != nil { + return "", nil, err + } + + if len(pkgs.Names) == 0 { + return "", nil, errors.New("package name is missing") + } + + var vars types.BuildVars + + if len(pkgs.Names) == 1 { + err = dec.DecodeVars(&vars) + if err != nil { + return "", nil, err + } + varsOfPackages = append(varsOfPackages, &vars) + + return vars.Name, varsOfPackages, nil + } + + var pkgNames []string + + if len(packages) != 0 { + pkgNames = packages + } else { + pkgNames = pkgs.Names + } + + for _, pkgName := range pkgNames { + var preVars types.BuildVarsPre + funcName := fmt.Sprintf("meta_%s", pkgName) + meta, ok := dec.GetFuncWithSubshell(funcName) + if !ok { + return "", nil, fmt.Errorf("func %s is missing", funcName) + } + r, err := meta(ctx) + if err != nil { + return "", nil, err + } + d := decoder.New(&distro.OSRelease{}, r) + err = d.DecodeVars(&preVars) + if err != nil { + return "", nil, err + } + vars := preVars.ToBuildVars() + vars.Name = pkgName + vars.Base = pkgs.BasePkgName + + varsOfPackages = append(varsOfPackages, &vars) + } + + return pkgs.BasePkgName, varsOfPackages, nil +} + +func (a *ALRSh) Path() string { + return a.path +} + +func (a *ALRSh) File() *syntax.File { + return a.file +} diff --git a/pkg/alrsh/gob.go b/pkg/alrsh/gob.go new file mode 100644 index 0000000..e1ad501 --- /dev/null +++ b/pkg/alrsh/gob.go @@ -0,0 +1,61 @@ +// 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 . + +package alrsh + +import ( + "bytes" + "encoding/gob" + + "mvdan.cc/sh/v3/syntax" + "mvdan.cc/sh/v3/syntax/typedjson" +) + +func (s *ALRSh) GobEncode() ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + if err := enc.Encode(s.path); err != nil { + return nil, err + } + var fileBuf bytes.Buffer + if err := typedjson.Encode(&fileBuf, s.file); err != nil { + return nil, err + } + fileData := fileBuf.Bytes() + if err := enc.Encode(fileData); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (s *ALRSh) GobDecode(data []byte) error { + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + if err := dec.Decode(&s.path); err != nil { + return err + } + var fileData []byte + if err := dec.Decode(&fileData); err != nil { + return err + } + fileReader := bytes.NewReader(fileData) + file, err := typedjson.Decode(fileReader) + if err != nil { + return err + } + s.file = file.(*syntax.File) + return nil +} diff --git a/pkg/alrsh/read.go b/pkg/alrsh/read.go new file mode 100644 index 0000000..360e44d --- /dev/null +++ b/pkg/alrsh/read.go @@ -0,0 +1,52 @@ +// 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 . + +package alrsh + +import ( + "fmt" + "io/fs" + "os" + + "mvdan.cc/sh/v3/syntax" +) + +type localFs struct{} + +func (fs *localFs) Open(name string) (fs.File, error) { + return os.Open(name) +} + +func ReadFromFS(fsys fs.FS, script string) (*ALRSh, error) { + fl, err := fsys.Open(script) + if err != nil { + return nil, fmt.Errorf("failed to open alr.sh: %w", err) + } + defer fl.Close() + + file, err := syntax.NewParser().Parse(fl, "alr.sh") + if err != nil { + return nil, err + } + return &ALRSh{ + file: file, + path: script, + }, nil +} + +func ReadFromLocal(script string) (*ALRSh, error) { + return ReadFromFS(&localFs{}, script) +} diff --git a/pkg/types/script.go b/pkg/types/script.go new file mode 100644 index 0000000..c167a6d --- /dev/null +++ b/pkg/types/script.go @@ -0,0 +1,66 @@ +// 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 . + +package types + +import ( + "bytes" + "encoding/gob" + + "mvdan.cc/sh/v3/syntax" + "mvdan.cc/sh/v3/syntax/typedjson" +) + +type ScriptFile struct { + File *syntax.File + Path string +} + +func (s *ScriptFile) GobEncode() ([]byte, error) { + var buf bytes.Buffer + enc := gob.NewEncoder(&buf) + if err := enc.Encode(s.Path); err != nil { + return nil, err + } + var fileBuf bytes.Buffer + if err := typedjson.Encode(&fileBuf, s.File); err != nil { + return nil, err + } + fileData := fileBuf.Bytes() + if err := enc.Encode(fileData); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func (s *ScriptFile) GobDecode(data []byte) error { + buf := bytes.NewBuffer(data) + dec := gob.NewDecoder(buf) + if err := dec.Decode(&s.Path); err != nil { + return err + } + var fileData []byte + if err := dec.Decode(&fileData); err != nil { + return err + } + fileReader := bytes.NewReader(fileData) + file, err := typedjson.Decode(fileReader) + if err != nil { + return err + } + s.File = file.(*syntax.File) + return nil +}