fix parsing overrides
This commit is contained in:
1
Makefile
1
Makefile
@ -21,6 +21,7 @@ build: check-no-root $(BIN)
|
||||
|
||||
export CGO_ENABLED := 0
|
||||
$(BIN):
|
||||
go generate ./...
|
||||
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
|
||||
|
||||
check-no-root:
|
||||
|
@ -11,7 +11,7 @@
|
||||
<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="14">coverage</text>
|
||||
<text x="86" y="15" fill="#010101" fill-opacity=".3">20.1%</text>
|
||||
<text x="86" y="14">20.1%</text>
|
||||
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.7%</text>
|
||||
<text x="86" y="14">19.7%</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B |
251
generators/alrsh-package/main.go
Normal file
251
generators/alrsh-package/main.go
Normal file
@ -0,0 +1,251 @@
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func resolvedStructGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||
contentTemplate := template.Must(template.New("").Parse(`
|
||||
type {{ .EntityNameLower }}Resolved struct {
|
||||
{{ .StructFields }}
|
||||
}
|
||||
`))
|
||||
|
||||
var structFieldsBuilder strings.Builder
|
||||
|
||||
for _, field := range fields {
|
||||
for _, name := range field.Names {
|
||||
// Поле с типом
|
||||
fieldTypeStr := exprToString(field.Type)
|
||||
|
||||
// Структура поля
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("\t")
|
||||
buf.WriteString(name.Name)
|
||||
buf.WriteString(" ")
|
||||
buf.WriteString(fieldTypeStr)
|
||||
|
||||
// Обработка json-тега
|
||||
jsonTag := ""
|
||||
if field.Tag != nil {
|
||||
raw := strings.Trim(field.Tag.Value, "`")
|
||||
tag := reflect.StructTag(raw)
|
||||
if val := tag.Get("json"); val != "" {
|
||||
jsonTag = val
|
||||
}
|
||||
}
|
||||
if jsonTag == "" {
|
||||
jsonTag = strings.ToLower(name.Name)
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(" `json:\"%s\"`", jsonTag))
|
||||
buf.WriteString("\n")
|
||||
structFieldsBuilder.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
params := struct {
|
||||
EntityNameLower string
|
||||
StructFields string
|
||||
}{
|
||||
EntityNameLower: "package",
|
||||
StructFields: structFieldsBuilder.String(),
|
||||
}
|
||||
|
||||
err := contentTemplate.Execute(buf, params)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func toResolvedFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||
contentTemplate := template.Must(template.New("").Parse(`
|
||||
func {{ .EntityName }}ToResolved(src *{{ .EntityName }}) {{ .EntityNameLower }}Resolved {
|
||||
return {{ .EntityNameLower }}Resolved{
|
||||
{{ .Assignments }}
|
||||
}
|
||||
}
|
||||
`))
|
||||
|
||||
var assignmentsBuilder strings.Builder
|
||||
|
||||
for _, field := range fields {
|
||||
for _, name := range field.Names {
|
||||
var assignBuf bytes.Buffer
|
||||
assignBuf.WriteString("\t\t")
|
||||
assignBuf.WriteString(name.Name)
|
||||
assignBuf.WriteString(": ")
|
||||
if isOverridableField(field.Type) {
|
||||
assignBuf.WriteString(fmt.Sprintf("src.%s.Resolved()", name.Name))
|
||||
} else {
|
||||
assignBuf.WriteString(fmt.Sprintf("src.%s", name.Name))
|
||||
}
|
||||
assignBuf.WriteString(",\n")
|
||||
assignmentsBuilder.Write(assignBuf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
params := struct {
|
||||
EntityName string
|
||||
EntityNameLower string
|
||||
Assignments string
|
||||
}{
|
||||
EntityName: "Package",
|
||||
EntityNameLower: "package",
|
||||
Assignments: assignmentsBuilder.String(),
|
||||
}
|
||||
|
||||
err := contentTemplate.Execute(buf, params)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
|
||||
contentTemplate := template.Must(template.New("").Parse(`
|
||||
func Resolve{{ .EntityName }}(pkg *{{ .EntityName }}, overrides []string) {
|
||||
{{.Code}}}
|
||||
`))
|
||||
|
||||
var codeBuilder strings.Builder
|
||||
|
||||
for _, field := range fields {
|
||||
for _, name := range field.Names {
|
||||
if isOverridableField(field.Type) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(fmt.Sprintf("\t\tpkg.%s.Resolve(overrides)\n", name.Name))
|
||||
codeBuilder.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params := struct {
|
||||
EntityName string
|
||||
Code string
|
||||
}{
|
||||
EntityName: "Package",
|
||||
Code: codeBuilder.String(),
|
||||
}
|
||||
|
||||
err := contentTemplate.Execute(buf, params)
|
||||
if err != nil {
|
||||
log.Fatalf("execute template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
path := os.Getenv("GOFILE")
|
||||
if path == "" {
|
||||
log.Fatal("GOFILE must be set")
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing file: %v", err)
|
||||
}
|
||||
|
||||
entityName := "Package" // имя структуры, которую анализируем
|
||||
|
||||
found := false
|
||||
|
||||
fields := make([]*ast.Field, 0)
|
||||
|
||||
// Ищем структуру с нужным именем
|
||||
for _, decl := range node.Decls {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok || genDecl.Tok != token.TYPE {
|
||||
continue
|
||||
}
|
||||
for _, spec := range genDecl.Specs {
|
||||
typeSpec := spec.(*ast.TypeSpec)
|
||||
if typeSpec.Name.Name != entityName {
|
||||
continue
|
||||
}
|
||||
structType, ok := typeSpec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fields = structType.Fields.List
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Fatalf("struct %s not found", entityName)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
|
||||
buf.WriteString("package alrsh")
|
||||
|
||||
resolvedStructGenerator(&buf, fields)
|
||||
toResolvedFuncGenerator(&buf, fields)
|
||||
resolveFuncGenerator(&buf, fields)
|
||||
|
||||
// Форматируем вывод
|
||||
formatted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatalf("formatting: %v", err)
|
||||
}
|
||||
|
||||
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
log.Fatalf("create file: %v", err)
|
||||
}
|
||||
_, err = outFile.Write(formatted)
|
||||
if err != nil {
|
||||
log.Fatalf("writing output: %v", err)
|
||||
}
|
||||
outFile.Close()
|
||||
}
|
||||
|
||||
func exprToString(expr ast.Expr) string {
|
||||
if t, ok := expr.(*ast.IndexExpr); ok {
|
||||
if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "OverridableField" {
|
||||
return exprToString(t.Index) // T
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := format.Node(&buf, token.NewFileSet(), expr); err != nil {
|
||||
return "<invalid>"
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func isOverridableField(expr ast.Expr) bool {
|
||||
indexExpr, ok := expr.(*ast.IndexExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ident, ok := indexExpr.X.(*ast.Ident)
|
||||
return ok && ident.Name == "OverridableField"
|
||||
}
|
3
go.mod
3
go.mod
@ -18,6 +18,7 @@ require (
|
||||
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
|
||||
github.com/go-git/go-billy/v5 v5.6.0
|
||||
github.com/go-git/go-git/v5 v5.13.0
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/goreleaser/nfpm/v2 v2.41.0
|
||||
github.com/hashicorp/go-hclog v0.14.1
|
||||
@ -38,7 +39,6 @@ require (
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.25.0
|
||||
mvdan.cc/sh/v3 v3.10.0
|
||||
xorm.io/xorm v1.3.9
|
||||
@ -139,6 +139,7 @@ require (
|
||||
google.golang.org/grpc v1.58.3 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -164,6 +164,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
16
info.go
16
info.go
@ -23,10 +23,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/jeandeaual/go-locale"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
|
||||
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
|
||||
@ -121,7 +121,6 @@ func InfoCmd() *cli.Command {
|
||||
systemLang = "en"
|
||||
}
|
||||
|
||||
if !all {
|
||||
info, err := distro.ParseOSRelease(ctx)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
|
||||
@ -134,22 +133,15 @@ func InfoCmd() *cli.Command {
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
if !all {
|
||||
alrsh.ResolvePackage(&pkg, names)
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
view := alrsh.NewPackageView(pkg)
|
||||
view.Resolved = !all
|
||||
err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
||||
}
|
||||
} else {
|
||||
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
|
||||
if err != nil {
|
||||
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("---")
|
||||
}
|
||||
|
||||
|
@ -367,7 +367,7 @@ func (b *Builder) BuildPackage(
|
||||
}
|
||||
|
||||
slog.Debug("ViewScript")
|
||||
slog.Debug("", "varsOfPackages", varsOfPackages)
|
||||
slog.Debug("", "varsOfPackages", varsOfPackages[0])
|
||||
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
@ -75,65 +74,30 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||
// DecodeVar decodes a variable to val using reflection.
|
||||
// Structs should use the "sh" struct tag.
|
||||
func (d *Decoder) DecodeVar(name string, val any) error {
|
||||
variable := d.getVar(name)
|
||||
origType := reflect.TypeOf(val).Elem()
|
||||
isOverridableField := strings.Contains(origType.String(), "OverridableField[")
|
||||
|
||||
if !isOverridableField {
|
||||
variable := d.getVarNoOverrides(name)
|
||||
if variable == nil {
|
||||
return VarNotFoundError{name}
|
||||
}
|
||||
|
||||
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: val, // передаем указатель на новое значение
|
||||
TagName: "sh",
|
||||
DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
|
||||
if strings.Contains(to.Type().String(), "alrsh.OverridableField") {
|
||||
if to.Kind() != reflect.Ptr && to.CanAddr() {
|
||||
to = to.Addr()
|
||||
if from.Kind() == reflect.Slice && to.Kind() == reflect.String {
|
||||
s, ok := from.Interface().([]string)
|
||||
if ok && len(s) == 1 {
|
||||
return s[0], nil
|
||||
}
|
||||
|
||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isNotSet := true
|
||||
|
||||
setMethod := to.MethodByName("Set")
|
||||
setResolvedMethod := to.MethodByName("SetResolved")
|
||||
|
||||
for _, varName := range names {
|
||||
val := d.getVarNoOverrides(varName)
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t := setMethod.Type().In(1)
|
||||
|
||||
newVal := from
|
||||
|
||||
if !newVal.Type().AssignableTo(t) {
|
||||
newVal = reflect.New(t)
|
||||
err = d.DecodeVar(name, newVal.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newVal = newVal.Elem()
|
||||
}
|
||||
|
||||
if isNotSet {
|
||||
setResolvedMethod.Call([]reflect.Value{newVal})
|
||||
}
|
||||
|
||||
override := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_")
|
||||
setMethod.Call([]reflect.Value{reflect.ValueOf(override), newVal})
|
||||
}
|
||||
|
||||
return to, nil
|
||||
}
|
||||
return from.Interface(), nil
|
||||
}),
|
||||
Result: val,
|
||||
TagName: "sh",
|
||||
})
|
||||
if err != nil {
|
||||
slog.Warn("err", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -145,6 +109,65 @@ func (d *Decoder) DecodeVar(name string, val any) error {
|
||||
default:
|
||||
return dec.Decode(variable.Str)
|
||||
}
|
||||
} else {
|
||||
vars := d.getVarsByPrefix(name)
|
||||
|
||||
if len(vars) == 0 {
|
||||
return VarNotFoundError{name}
|
||||
}
|
||||
|
||||
reflectVal := reflect.ValueOf(val)
|
||||
overridableVal := reflect.ValueOf(val).Elem()
|
||||
|
||||
dataField := overridableVal.FieldByName("data")
|
||||
if !dataField.IsValid() {
|
||||
return fmt.Errorf("data field not found in OverridableField")
|
||||
}
|
||||
mapType := dataField.Type() // map[string]T
|
||||
elemType := mapType.Elem() // T
|
||||
|
||||
var overridablePtr reflect.Value
|
||||
if reflectVal.Kind() == reflect.Ptr {
|
||||
overridablePtr = reflectVal
|
||||
} else {
|
||||
if !reflectVal.CanAddr() {
|
||||
return fmt.Errorf("OverridableField value is not addressable")
|
||||
}
|
||||
overridablePtr = reflectVal.Addr()
|
||||
}
|
||||
|
||||
setValue := overridablePtr.MethodByName("Set")
|
||||
if !setValue.IsValid() {
|
||||
return fmt.Errorf("method Set not found on OverridableField")
|
||||
}
|
||||
|
||||
for _, v := range vars {
|
||||
varName := v.Name
|
||||
|
||||
key := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_")
|
||||
newVal := reflect.New(elemType)
|
||||
|
||||
if err := d.DecodeVar(varName, newVal.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyValue := reflect.ValueOf(key)
|
||||
setValue.Call([]reflect.Value{keyValue, newVal.Elem()})
|
||||
}
|
||||
|
||||
resolveValue := overridablePtr.MethodByName("Resolve")
|
||||
if !resolveValue.IsValid() {
|
||||
return fmt.Errorf("method Resolve not found on OverridableField")
|
||||
}
|
||||
|
||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resolveValue.Call([]reflect.Value{reflect.ValueOf(names)})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeVars decodes all variables to val using reflection.
|
||||
@ -284,23 +307,6 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getVar gets a variable based on its name, taking into account
|
||||
// override variables and nameref variables.
|
||||
func (d *Decoder) getVar(name string) *expand.Variable {
|
||||
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, varName := range names {
|
||||
res := d.getVarNoOverrides(varName)
|
||||
if res != nil {
|
||||
return res
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
|
||||
val, ok := d.Runner.Vars[name]
|
||||
if ok {
|
||||
@ -318,6 +324,32 @@ func (d *Decoder) getVarNoOverrides(name string) *expand.Variable {
|
||||
return nil
|
||||
}
|
||||
|
||||
type vars struct {
|
||||
Name string
|
||||
Value *expand.Variable
|
||||
}
|
||||
|
||||
func (d *Decoder) getVarsByPrefix(prefix string) []*vars {
|
||||
result := make([]*vars, 0)
|
||||
for name, val := range d.Runner.Vars {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
continue
|
||||
}
|
||||
switch prefix {
|
||||
case "auto_req":
|
||||
if strings.HasPrefix(name, "auto_req_skiplist") {
|
||||
continue
|
||||
}
|
||||
case "auto_prov":
|
||||
if strings.HasPrefix(name, "auto_prov_skiplist") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
result = append(result, &vars{name, &val})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func IsTruthy(value string) bool {
|
||||
value = strings.ToLower(strings.TrimSpace(value))
|
||||
return value == "true" || value == "yes" || value == "1"
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
|
||||
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
|
||||
)
|
||||
|
||||
@ -40,7 +41,7 @@ type BuildVars struct {
|
||||
Version string `sh:"version,required"`
|
||||
Release int `sh:"release,required"`
|
||||
Epoch uint `sh:"epoch"`
|
||||
Description string `sh:"desc"`
|
||||
Description alrsh.OverridableField[string] `sh:"desc"`
|
||||
Homepage string `sh:"homepage"`
|
||||
Maintainer string `sh:"maintainer"`
|
||||
Architectures []string `sh:"architectures"`
|
||||
@ -48,8 +49,8 @@ type BuildVars struct {
|
||||
Provides []string `sh:"provides"`
|
||||
Conflicts []string `sh:"conflicts"`
|
||||
Depends []string `sh:"deps"`
|
||||
BuildDepends []string `sh:"build_deps"`
|
||||
Replaces []string `sh:"replaces"`
|
||||
BuildDepends alrsh.OverridableField[[]string] `sh:"build_deps"`
|
||||
Replaces alrsh.OverridableField[[]string] `sh:"replaces"`
|
||||
}
|
||||
|
||||
const testScript = `
|
||||
@ -117,18 +118,30 @@ func TestDecodeVars(t *testing.T) {
|
||||
Version: "0.0.1",
|
||||
Release: 1,
|
||||
Epoch: 2,
|
||||
Description: "Test package",
|
||||
Description: alrsh.OverridableFromMap(map[string]string{
|
||||
"": "Test package",
|
||||
}),
|
||||
Homepage: "https://gitea.plemya-x.ru/xpamych/ALR",
|
||||
Maintainer: "Евгений Храмов <xpamych@yandex.ru>",
|
||||
Architectures: []string{"arm64", "amd64"},
|
||||
Licenses: []string{"GPL-3.0-or-later"},
|
||||
Provides: []string{"test"},
|
||||
Conflicts: []string{"test"},
|
||||
Replaces: []string{"test-legacy"},
|
||||
Replaces: alrsh.OverridableFromMap(map[string][]string{
|
||||
"": {"test-old"},
|
||||
"test_os": {"test-legacy"},
|
||||
}),
|
||||
Depends: []string{"sudo"},
|
||||
BuildDepends: []string{"go"},
|
||||
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
|
||||
"": {"golang"},
|
||||
"arch": {"go"},
|
||||
}),
|
||||
}
|
||||
|
||||
expected.Description.SetResolved("Test package")
|
||||
expected.Replaces.SetResolved([]string{"test-legacy"})
|
||||
expected.BuildDepends.SetResolved([]string{"go"})
|
||||
|
||||
if !reflect.DeepEqual(bv, expected) {
|
||||
t.Errorf("Expected %v, got %v", expected, bv)
|
||||
}
|
||||
|
@ -138,11 +138,11 @@ msgstr ""
|
||||
msgid "Can't detect system language"
|
||||
msgstr ""
|
||||
|
||||
#: info.go:135
|
||||
#: info.go:134
|
||||
msgid "Error resolving overrides"
|
||||
msgstr ""
|
||||
|
||||
#: info.go:144 info.go:149
|
||||
#: info.go:143
|
||||
msgid "Error encoding script variables"
|
||||
msgstr ""
|
||||
|
||||
|
@ -145,11 +145,11 @@ msgstr "Ошибка при поиске пакетов"
|
||||
msgid "Can't detect system language"
|
||||
msgstr "Ошибка при определении языка системы"
|
||||
|
||||
#: info.go:135
|
||||
#: info.go:134
|
||||
msgid "Error resolving overrides"
|
||||
msgstr "Ошибка устранения переорпеделений"
|
||||
|
||||
#: info.go:144 info.go:149
|
||||
#: info.go:143
|
||||
msgid "Error encoding script variables"
|
||||
msgstr "Ошибка кодирования переменных скрита"
|
||||
|
||||
|
@ -68,10 +68,12 @@ func (o *OverridableField[T]) Resolve(overrides []string) {
|
||||
for _, override := range overrides {
|
||||
if v, ok := o.Has(override); ok {
|
||||
o.SetResolved(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Database serialization (JSON)
|
||||
func (f *OverridableField[T]) ToDB() ([]byte, error) {
|
||||
var data map[string]T
|
||||
|
||||
@ -103,6 +105,7 @@ func (f *OverridableField[T]) FromDB(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gob serialization
|
||||
type overridableFieldGobPayload[T any] struct {
|
||||
Data map[string]T
|
||||
Resolved T
|
||||
@ -136,6 +139,48 @@ func (f *OverridableField[T]) GobDecode(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type overridableFieldJSONPayload[T any] struct {
|
||||
Resolved *T `json:"resolved,omitempty,omitzero"`
|
||||
Data map[string]T `json:"overrides,omitempty,omitzero"`
|
||||
}
|
||||
|
||||
func (f OverridableField[T]) MarshalJSON() ([]byte, error) {
|
||||
data := make(map[string]T)
|
||||
|
||||
for k, v := range f.data {
|
||||
if k == "" {
|
||||
data["default"] = v
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
payload := overridableFieldJSONPayload[T]{
|
||||
Data: data,
|
||||
Resolved: &f.resolved,
|
||||
}
|
||||
|
||||
return json.Marshal(payload)
|
||||
}
|
||||
|
||||
func (f *OverridableField[T]) UnmarshalJSON(data []byte) error {
|
||||
var payload overridableFieldJSONPayload[T]
|
||||
if err := json.Unmarshal(data, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if payload.Data == nil {
|
||||
payload.Data = make(map[string]T)
|
||||
}
|
||||
|
||||
f.data = payload.Data
|
||||
if payload.Resolved != nil {
|
||||
f.resolved = *payload.Resolved
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func OverridableFromMap[T any](data map[string]T) OverridableField[T] {
|
||||
if data == nil {
|
||||
data = make(map[string]T)
|
||||
|
@ -14,9 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:generate go run ../../generators/alrsh-package
|
||||
|
||||
package alrsh
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -39,38 +42,38 @@ func ParseNames(dec *decoder.Decoder) (*PackageNames, error) {
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
Repository string `xorm:"pk 'repository'"`
|
||||
Name string `xorm:"pk 'name'"`
|
||||
BasePkgName string `xorm:"notnull 'basepkg_name'"`
|
||||
Repository string `xorm:"pk 'repository'" json:"repository"`
|
||||
Name string `xorm:"pk 'name'" json:"name"`
|
||||
BasePkgName string `xorm:"notnull 'basepkg_name'" json:"basepkg_name"`
|
||||
|
||||
Version string `sh:"version" xorm:"notnull 'version'"`
|
||||
Release int `sh:"release" xorm:"notnull 'release'"`
|
||||
Epoch uint `sh:"epoch" xorm:"'epoch'"`
|
||||
Architectures []string `sh:"architectures" xorm:"json 'architectures'"`
|
||||
Licenses []string `sh:"license" xorm:"json 'licenses'"`
|
||||
Provides []string `sh:"provides" xorm:"json 'provides'"`
|
||||
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'"`
|
||||
Replaces []string `sh:"replaces" xorm:"json 'replaces'"`
|
||||
Version string `sh:"version" xorm:"notnull 'version'" json:"version"`
|
||||
Release int `sh:"release" xorm:"notnull 'release'" json:"release"`
|
||||
Epoch uint `sh:"epoch" xorm:"'epoch'" json:"epoch"`
|
||||
Architectures []string `sh:"architectures" xorm:"json 'architectures'" json:"architectures"`
|
||||
Licenses []string `sh:"license" xorm:"json 'licenses'" json:"license"`
|
||||
Provides []string `sh:"provides" xorm:"json 'provides'" json:"provides"`
|
||||
Conflicts []string `sh:"conflicts" xorm:"json 'conflicts'" json:"conflicts"`
|
||||
Replaces []string `sh:"replaces" xorm:"json 'replaces'" json:"replaces"`
|
||||
|
||||
Summary OverridableField[string] `sh:"summary" xorm:"'summary'"`
|
||||
Description OverridableField[string] `sh:"desc" xorm:"'description'"`
|
||||
Group OverridableField[string] `sh:"group" xorm:"'group_name'"`
|
||||
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'"`
|
||||
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'"`
|
||||
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'"`
|
||||
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'"`
|
||||
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'"`
|
||||
Sources OverridableField[[]string] `sh:"sources" xorm:"-"`
|
||||
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-"`
|
||||
Backup OverridableField[[]string] `sh:"backup" xorm:"-"`
|
||||
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-"`
|
||||
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-"`
|
||||
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
|
||||
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
|
||||
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
|
||||
Summary OverridableField[string] `sh:"summary" xorm:"'summary'" json:"summary"`
|
||||
Description OverridableField[string] `sh:"desc" xorm:"'description'" json:"description"`
|
||||
Group OverridableField[string] `sh:"group" xorm:"'group_name'" json:"group"`
|
||||
Homepage OverridableField[string] `sh:"homepage" xorm:"'homepage'" json:"homepage"`
|
||||
Maintainer OverridableField[string] `sh:"maintainer" xorm:"'maintainer'" json:"maintainer"`
|
||||
Depends OverridableField[[]string] `sh:"deps" xorm:"'depends'" json:"deps"`
|
||||
BuildDepends OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'" json:"build_deps"`
|
||||
OptDepends OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'" json:"opt_deps,omitempty"`
|
||||
Sources OverridableField[[]string] `sh:"sources" xorm:"-" json:"sources"`
|
||||
Checksums OverridableField[[]string] `sh:"checksums" xorm:"-" json:"checksums,omitempty"`
|
||||
Backup OverridableField[[]string] `sh:"backup" xorm:"-" json:"backup"`
|
||||
Scripts OverridableField[Scripts] `sh:"scripts" xorm:"-" json:"scripts,omitempty"`
|
||||
AutoReq OverridableField[[]string] `sh:"auto_req" xorm:"-" json:"auto_req"`
|
||||
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-" json:"auto_prov"`
|
||||
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-" json:"auto_req_skiplist,omitempty"`
|
||||
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-" json:"auto_prov_skiplist,omitempty"`
|
||||
|
||||
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-"`
|
||||
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-"`
|
||||
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-" json:"firejailed"`
|
||||
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-" json:"firejail_profiles,omitempty"`
|
||||
}
|
||||
|
||||
type Scripts struct {
|
||||
@ -84,25 +87,70 @@ type Scripts struct {
|
||||
PostTrans string `sh:"posttrans"`
|
||||
}
|
||||
|
||||
func ResolvePackage(p *Package, overrides []string) {
|
||||
val := reflect.ValueOf(p).Elem()
|
||||
typ := val.Type()
|
||||
func (p Package) MarshalJSONWithOptions(includeOverrides bool) ([]byte, error) {
|
||||
// Сначала сериализуем обычным способом для получения базовой структуры
|
||||
type PackageAlias Package
|
||||
baseData, err := json.Marshal(PackageAlias(p))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range val.NumField() {
|
||||
field := val.Field(i)
|
||||
fieldType := typ.Field(i)
|
||||
// Десериализуем в map для модификации
|
||||
var result map[string]json.RawMessage
|
||||
if err := json.Unmarshal(baseData, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !field.CanInterface() {
|
||||
// Теперь заменяем OverridableField поля
|
||||
v := reflect.ValueOf(p)
|
||||
t := reflect.TypeOf(p)
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
fieldType := t.Field(i)
|
||||
|
||||
jsonTag := fieldType.Tag.Get("json")
|
||||
if jsonTag == "" || jsonTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Struct && strings.HasPrefix(fieldType.Type.String(), "alrsh.OverridableField") {
|
||||
of := field.Addr().Interface()
|
||||
if res, ok := of.(interface {
|
||||
Resolve([]string)
|
||||
}); ok {
|
||||
res.Resolve(overrides)
|
||||
fieldName := jsonTag
|
||||
if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 {
|
||||
fieldName = jsonTag[:commaIdx]
|
||||
}
|
||||
|
||||
if field.Type().Name() == "OverridableField" ||
|
||||
(field.Type().Kind() == reflect.Struct &&
|
||||
strings.Contains(field.Type().String(), "OverridableField")) {
|
||||
|
||||
fieldPtr := field.Addr()
|
||||
|
||||
resolvedMethod := fieldPtr.MethodByName("Resolved")
|
||||
if resolvedMethod.IsValid() {
|
||||
resolved := resolvedMethod.Call(nil)[0]
|
||||
|
||||
fieldData := map[string]interface{}{
|
||||
"resolved": resolved.Interface(),
|
||||
}
|
||||
|
||||
if includeOverrides {
|
||||
allMethod := field.MethodByName("All")
|
||||
if allMethod.IsValid() {
|
||||
overrides := allMethod.Call(nil)[0]
|
||||
if !overrides.IsNil() && overrides.Len() > 0 {
|
||||
fieldData["overrides"] = overrides.Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldJSON, err := json.Marshal(fieldData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[fieldName] = json.RawMessage(fieldJSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
105
pkg/alrsh/package_gen.go
Normal file
105
pkg/alrsh/package_gen.go
Normal file
@ -0,0 +1,105 @@
|
||||
// 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/>.
|
||||
|
||||
// DO NOT EDIT MANUALLY. This file is generated.
|
||||
package alrsh
|
||||
|
||||
type packageResolved struct {
|
||||
Repository string `json:"repository"`
|
||||
Name string `json:"name"`
|
||||
BasePkgName string `json:"basepkg_name"`
|
||||
Version string `json:"version"`
|
||||
Release int `json:"release"`
|
||||
Epoch uint `json:"epoch"`
|
||||
Architectures []string `json:"architectures"`
|
||||
Licenses []string `json:"license"`
|
||||
Provides []string `json:"provides"`
|
||||
Conflicts []string `json:"conflicts"`
|
||||
Replaces []string `json:"replaces"`
|
||||
Summary string `json:"summary"`
|
||||
Description string `json:"description"`
|
||||
Group string `json:"group"`
|
||||
Homepage string `json:"homepage"`
|
||||
Maintainer string `json:"maintainer"`
|
||||
Depends []string `json:"deps"`
|
||||
BuildDepends []string `json:"build_deps"`
|
||||
OptDepends []string `json:"opt_deps,omitempty"`
|
||||
Sources []string `json:"sources"`
|
||||
Checksums []string `json:"checksums,omitempty"`
|
||||
Backup []string `json:"backup"`
|
||||
Scripts Scripts `json:"scripts,omitempty"`
|
||||
AutoReq []string `json:"auto_req"`
|
||||
AutoProv []string `json:"auto_prov"`
|
||||
AutoReqSkipList []string `json:"auto_req_skiplist,omitempty"`
|
||||
AutoProvSkipList []string `json:"auto_prov_skiplist,omitempty"`
|
||||
FireJailed bool `json:"firejailed"`
|
||||
FireJailProfiles map[string]string `json:"firejail_profiles,omitempty"`
|
||||
}
|
||||
|
||||
func PackageToResolved(src *Package) packageResolved {
|
||||
return packageResolved{
|
||||
Repository: src.Repository,
|
||||
Name: src.Name,
|
||||
BasePkgName: src.BasePkgName,
|
||||
Version: src.Version,
|
||||
Release: src.Release,
|
||||
Epoch: src.Epoch,
|
||||
Architectures: src.Architectures,
|
||||
Licenses: src.Licenses,
|
||||
Provides: src.Provides,
|
||||
Conflicts: src.Conflicts,
|
||||
Replaces: src.Replaces,
|
||||
Summary: src.Summary.Resolved(),
|
||||
Description: src.Description.Resolved(),
|
||||
Group: src.Group.Resolved(),
|
||||
Homepage: src.Homepage.Resolved(),
|
||||
Maintainer: src.Maintainer.Resolved(),
|
||||
Depends: src.Depends.Resolved(),
|
||||
BuildDepends: src.BuildDepends.Resolved(),
|
||||
OptDepends: src.OptDepends.Resolved(),
|
||||
Sources: src.Sources.Resolved(),
|
||||
Checksums: src.Checksums.Resolved(),
|
||||
Backup: src.Backup.Resolved(),
|
||||
Scripts: src.Scripts.Resolved(),
|
||||
AutoReq: src.AutoReq.Resolved(),
|
||||
AutoProv: src.AutoProv.Resolved(),
|
||||
AutoReqSkipList: src.AutoReqSkipList.Resolved(),
|
||||
AutoProvSkipList: src.AutoProvSkipList.Resolved(),
|
||||
FireJailed: src.FireJailed.Resolved(),
|
||||
FireJailProfiles: src.FireJailProfiles.Resolved(),
|
||||
}
|
||||
}
|
||||
|
||||
func ResolvePackage(pkg *Package, overrides []string) {
|
||||
pkg.Summary.Resolve(overrides)
|
||||
pkg.Description.Resolve(overrides)
|
||||
pkg.Group.Resolve(overrides)
|
||||
pkg.Homepage.Resolve(overrides)
|
||||
pkg.Maintainer.Resolve(overrides)
|
||||
pkg.Depends.Resolve(overrides)
|
||||
pkg.BuildDepends.Resolve(overrides)
|
||||
pkg.OptDepends.Resolve(overrides)
|
||||
pkg.Sources.Resolve(overrides)
|
||||
pkg.Checksums.Resolve(overrides)
|
||||
pkg.Backup.Resolve(overrides)
|
||||
pkg.Scripts.Resolve(overrides)
|
||||
pkg.AutoReq.Resolve(overrides)
|
||||
pkg.AutoProv.Resolve(overrides)
|
||||
pkg.AutoReqSkipList.Resolve(overrides)
|
||||
pkg.AutoProvSkipList.Resolve(overrides)
|
||||
pkg.FireJailed.Resolve(overrides)
|
||||
pkg.FireJailProfiles.Resolve(overrides)
|
||||
}
|
37
pkg/alrsh/view.go
Normal file
37
pkg/alrsh/view.go
Normal file
@ -0,0 +1,37 @@
|
||||
// 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 alrsh
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type PackageView struct {
|
||||
pkg Package
|
||||
|
||||
Resolved bool
|
||||
}
|
||||
|
||||
func NewPackageView(v Package) PackageView {
|
||||
return PackageView{pkg: v}
|
||||
}
|
||||
|
||||
func (p PackageView) MarshalJSON() ([]byte, error) {
|
||||
if p.Resolved {
|
||||
return json.Marshal(PackageToResolved(&p.pkg))
|
||||
} else {
|
||||
return json.Marshal(p.pkg)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user