ALR

Форк
1
223 строки · 5.3 Кб
1
/*
2
 * ALR - Any Linux Repository
3
 * Copyright (C) 2024 Евгений Храмов
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
package decoder
20

21
import (
22
	"context"
23
	"errors"
24
	"reflect"
25
	"strings"
26

27
	"github.com/mitchellh/mapstructure"
28
	"plemya-x.ru/alr/internal/overrides"
29
	"plemya-x.ru/alr/pkg/distro"
30
	"golang.org/x/exp/slices"
31
	"mvdan.cc/sh/v3/expand"
32
	"mvdan.cc/sh/v3/interp"
33
	"mvdan.cc/sh/v3/syntax"
34
)
35

36
var ErrNotPointerToStruct = errors.New("val must be a pointer to a struct")
37

38
type VarNotFoundError struct {
39
	name string
40
}
41

42
func (nfe VarNotFoundError) Error() string {
43
	return "required variable '" + nfe.name + "' could not be found"
44
}
45

46
type InvalidTypeError struct {
47
	name    string
48
	vartype string
49
	exptype string
50
}
51

52
func (ite InvalidTypeError) Error() string {
53
	return "variable '" + ite.name + "' is of type " + ite.vartype + ", but " + ite.exptype + " is expected"
54
}
55

56
// Decoder provides methods for decoding variable values
57
type Decoder struct {
58
	info   *distro.OSRelease
59
	Runner *interp.Runner
60
	// Enable distro overrides (true by default)
61
	Overrides bool
62
	// Enable using like distros for overrides
63
	LikeDistros bool
64
}
65

66
// New creates a new variable decoder
67
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
68
	return &Decoder{info, runner, true, len(info.Like) > 0}
69
}
70

71
// DecodeVar decodes a variable to val using reflection.
72
// Structs should use the "sh" struct tag.
73
func (d *Decoder) DecodeVar(name string, val any) error {
74
	variable := d.getVar(name)
75
	if variable == nil {
76
		return VarNotFoundError{name}
77
	}
78

79
	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
80
		WeaklyTypedInput: true,
81
		DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) {
82
			if strings.Contains(to.Type().String(), "db.JSON") {
83
				valType := to.FieldByName("Val").Type()
84
				if !from.Type().AssignableTo(valType) {
85
					return nil, InvalidTypeError{name, from.Type().String(), valType.String()}
86
				}
87

88
				to.FieldByName("Val").Set(from)
89
				return to, nil
90
			}
91
			return from.Interface(), nil
92
		}),
93
		Result:  val,
94
		TagName: "sh",
95
	})
96
	if err != nil {
97
		return err
98
	}
99

100
	switch variable.Kind {
101
	case expand.Indexed:
102
		return dec.Decode(variable.List)
103
	case expand.Associative:
104
		return dec.Decode(variable.Map)
105
	default:
106
		return dec.Decode(variable.Str)
107
	}
108
}
109

110
// DecodeVars decodes all variables to val using reflection.
111
// Structs should use the "sh" struct tag.
112
func (d *Decoder) DecodeVars(val any) error {
113
	valKind := reflect.TypeOf(val).Kind()
114
	if valKind != reflect.Pointer {
115
		return ErrNotPointerToStruct
116
	} else {
117
		elemKind := reflect.TypeOf(val).Elem().Kind()
118
		if elemKind != reflect.Struct {
119
			return ErrNotPointerToStruct
120
		}
121
	}
122

123
	rVal := reflect.ValueOf(val).Elem()
124

125
	for i := 0; i < rVal.NumField(); i++ {
126
		field := rVal.Field(i)
127
		fieldType := rVal.Type().Field(i)
128

129
		if !fieldType.IsExported() {
130
			continue
131
		}
132

133
		name := fieldType.Name
134
		tag := fieldType.Tag.Get("sh")
135
		required := false
136
		if tag != "" {
137
			if strings.Contains(tag, ",") {
138
				splitTag := strings.Split(tag, ",")
139
				name = splitTag[0]
140

141
				if len(splitTag) > 1 {
142
					if slices.Contains(splitTag, "required") {
143
						required = true
144
					}
145
				}
146
			} else {
147
				name = tag
148
			}
149
		}
150

151
		newVal := reflect.New(field.Type())
152
		err := d.DecodeVar(name, newVal.Interface())
153
		if _, ok := err.(VarNotFoundError); ok && !required {
154
			continue
155
		} else if err != nil {
156
			return err
157
		}
158

159
		field.Set(newVal.Elem())
160
	}
161

162
	return nil
163
}
164

165
type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
166

167
// GetFunc returns a function corresponding to a bash function
168
// with the given name
169
func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
170
	fn := d.getFunc(name)
171
	if fn == nil {
172
		return nil, false
173
	}
174

175
	return func(ctx context.Context, opts ...interp.RunnerOption) error {
176
		sub := d.Runner.Subshell()
177
		for _, opt := range opts {
178
			opt(sub)
179
		}
180
		return sub.Run(ctx, fn)
181
	}, true
182
}
183

184
func (d *Decoder) getFunc(name string) *syntax.Stmt {
185
	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
186
	if err != nil {
187
		return nil
188
	}
189

190
	for _, fnName := range names {
191
		fn, ok := d.Runner.Funcs[fnName]
192
		if ok {
193
			return fn
194
		}
195
	}
196
	return nil
197
}
198

199
// getVar gets a variable based on its name, taking into account
200
// override variables and nameref variables.
201
func (d *Decoder) getVar(name string) *expand.Variable {
202
	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
203
	if err != nil {
204
		return nil
205
	}
206

207
	for _, varName := range names {
208
		val, ok := d.Runner.Vars[varName]
209
		if ok {
210
			// Resolve nameref variables
211
			_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
212
				if val, ok := d.Runner.Vars[s]; ok {
213
					return val.String()
214
				}
215
				return ""
216
			}))
217
			val = resolved
218

219
			return &val
220
		}
221
	}
222
	return nil
223
}
224

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.