feat: config command
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 5m5s
Update alr-git / changelog (push) Successful in 27s

This commit is contained in:
2025-06-29 21:26:00 +03:00
parent 6355f25089
commit d77ca4c384
15 changed files with 738 additions and 160 deletions

View File

@ -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">19.8%</text>
<text x="86" y="14">19.8%</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.2%</text>
<text x="86" y="14">19.2%</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

227
config.go Normal file
View File

@ -0,0 +1,227 @@
// 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 (
"fmt"
"strconv"
"strings"
"github.com/goccy/go-yaml"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
func ConfigCmd() *cli.Command {
return &cli.Command{
Name: "config",
Usage: gotext.Get("Manage config"),
Subcommands: []*cli.Command{
ShowCmd(),
SetConfig(),
GetConfig(),
},
}
}
func ShowCmd() *cli.Command {
return &cli.Command{
Name: "show",
Usage: gotext.Get("Show config"),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
return nil
}),
Action: func(c *cli.Context) error {
deps, err := appbuilder.
New(c.Context).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
content, err := deps.Cfg.ToYAML()
if err != nil {
return err
}
fmt.Println(content)
return nil
},
}
}
var configKeys = []string{
"rootCmd",
"useRootCmd",
"pagerStyle",
"autoPull",
"logLevel",
"ignorePkgUpdates",
}
func SetConfig() *cli.Command {
return &cli.Command{
Name: "set",
Usage: gotext.Get("Set config value"),
ArgsUsage: gotext.Get("<key> <value>"),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if c.Args().Len() == 0 {
for _, key := range configKeys {
fmt.Println(key)
}
return nil
}
return nil
}),
Action: utils.RootNeededAction(func(c *cli.Context) error {
if c.Args().Len() < 2 {
return cliutils.FormatCliExit("missing args", nil)
}
key := c.Args().Get(0)
value := c.Args().Get(1)
deps, err := appbuilder.
New(c.Context).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
switch key {
case "rootCmd":
deps.Cfg.System.SetRootCmd(value)
case "useRootCmd":
boolValue, err := strconv.ParseBool(value)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err)
}
deps.Cfg.System.SetUseRootCmd(boolValue)
case "pagerStyle":
deps.Cfg.System.SetPagerStyle(value)
case "autoPull":
boolValue, err := strconv.ParseBool(value)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err)
}
deps.Cfg.System.SetAutoPull(boolValue)
case "logLevel":
deps.Cfg.System.SetLogLevel(value)
case "ignorePkgUpdates":
var updates []string
if value != "" {
updates = strings.Split(value, ",")
for i, update := range updates {
updates[i] = strings.TrimSpace(update)
}
}
deps.Cfg.System.SetIgnorePkgUpdates(updates)
case "repo", "repos":
return cliutils.FormatCliExit(gotext.Get("use 'repo add/remove' commands to manage repositories"), nil)
default:
return cliutils.FormatCliExit(gotext.Get("unknown config key: %s", key), nil)
}
if err := deps.Cfg.System.Save(); err != nil {
return cliutils.FormatCliExit(gotext.Get("failed to save config"), err)
}
fmt.Println(gotext.Get("Successfully set %s = %s", key, value))
return nil
}),
}
}
func GetConfig() *cli.Command {
return &cli.Command{
Name: "get",
Usage: gotext.Get("Get config value"),
ArgsUsage: gotext.Get("<key>"),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if c.Args().Len() == 0 {
for _, key := range configKeys {
fmt.Println(key)
}
return nil
}
return nil
}),
Action: func(c *cli.Context) error {
deps, err := appbuilder.
New(c.Context).
WithConfig().
Build()
if err != nil {
return err
}
defer deps.Defer()
if c.Args().Len() == 0 {
content, err := deps.Cfg.ToYAML()
if err != nil {
return cliutils.FormatCliExit("failed to serialize config", err)
}
fmt.Print(content)
return nil
}
key := c.Args().Get(0)
switch key {
case "rootCmd":
fmt.Println(deps.Cfg.RootCmd())
case "useRootCmd":
fmt.Println(deps.Cfg.UseRootCmd())
case "pagerStyle":
fmt.Println(deps.Cfg.PagerStyle())
case "autoPull":
fmt.Println(deps.Cfg.AutoPull())
case "logLevel":
fmt.Println(deps.Cfg.LogLevel())
case "ignorePkgUpdates":
updates := deps.Cfg.IgnorePkgUpdates()
if len(updates) == 0 {
fmt.Println("[]")
} else {
fmt.Println(strings.Join(updates, ", "))
}
case "repo", "repos":
repos := deps.Cfg.Repos()
if len(repos) == 0 {
fmt.Println("[]")
} else {
repoData, err := yaml.Marshal(repos)
if err != nil {
return cliutils.FormatCliExit("failed to serialize repos", err)
}
fmt.Print(string(repoData))
}
default:
return cliutils.FormatCliExit(gotext.Get("unknown config key: %s", key), nil)
}
return nil
},
}
}

View File

@ -66,7 +66,7 @@ func TestE2EAlrAddRepo(t *testing.T) {
"cat /etc/alr/alr.toml",
), e2e.WithExecOptionStdout(&buf))
assert.NoError(t, err)
assert.Contains(t, buf.String(), "rootCmd")
assert.Contains(t, buf.String(), "repo = []")
},
)
}

View File

@ -0,0 +1,47 @@
// 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/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue95ConfigCommand(t *testing.T) {
dockerMultipleRun(
t,
"issue-95-config-command",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: true\"")
execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: true\"")
execShouldError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull\"")
execShouldNoError(t, r, "alr", "config", "get", "autoPull")
execShouldError(t, r, "alr", "config", "set", "autoPull")
execShouldNoError(t, r, "sudo", "alr", "config", "set", "autoPull", "false")
execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: false\"")
execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: false\"")
execShouldNoError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull = false\"")
execShouldNoError(t, r, "alr", "config", "set", "autoPull", "true")
execShouldNoError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull = true\"")
},
)
}

25
go.mod
View File

@ -29,7 +29,7 @@ require (
github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.1.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/stretchr/testify v1.10.0
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
github.com/urfave/cli/v2 v2.25.7
@ -37,7 +37,7 @@ require (
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sys v0.31.0
golang.org/x/sys v0.33.0
golang.org/x/text v0.23.0
modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.10.0
@ -75,8 +75,11 @@ require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@ -84,7 +87,7 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/goreleaser/chglog v0.6.1 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
@ -98,6 +101,15 @@ require (
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/parsers/json v1.0.0 // indirect
github.com/knadh/koanf/parsers/toml/v2 v2.2.0 // indirect
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
github.com/knadh/koanf/providers/env v1.1.0 // indirect
github.com/knadh/koanf/providers/file v1.2.0 // indirect
github.com/knadh/koanf/providers/rawbytes v1.0.0 // indirect
github.com/knadh/koanf/providers/structs v1.0.0 // indirect
github.com/knadh/koanf/v2 v2.2.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
@ -121,7 +133,7 @@ require (
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
@ -132,11 +144,12 @@ require (
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/tools v0.23.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
google.golang.org/grpc v1.67.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

62
go.sum
View File

@ -84,8 +84,8 @@ github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTli
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
@ -139,9 +139,13 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@ -160,6 +164,8 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
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-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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=
@ -212,8 +218,8 @@ github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQ
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
@ -273,6 +279,24 @@ github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/json v1.0.0 h1:1pVR1JhMwbqSg5ICzU+surJmeBbdT4bQm7jjgnA+f8o=
github.com/knadh/koanf/parsers/json v1.0.0/go.mod h1:zb5WtibRdpxSoSJfXysqGbVxvbszdlroWDHGdDkkEYU=
github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A=
github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI=
github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE=
github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A=
github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc=
github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY=
github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U=
github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI=
github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo=
github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4=
github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w=
github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE=
github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -341,8 +365,10 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
@ -388,19 +414,15 @@ github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@ -500,8 +522,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -541,6 +563,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -601,8 +625,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -616,8 +638,8 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -625,8 +647,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8=
google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=

View File

@ -20,13 +20,12 @@
package config
import (
"log/slog"
"os"
"fmt"
"path/filepath"
"reflect"
"github.com/caarlos0/env"
"github.com/pelletier/go-toml/v2"
"github.com/goccy/go-yaml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
@ -35,74 +34,65 @@ import (
type ALRConfig struct {
cfg *types.Config
paths *Paths
}
var defaultConfig = &types.Config{
RootCmd: "sudo",
UseRootCmd: true,
PagerStyle: "native",
IgnorePkgUpdates: []string{},
AutoPull: true,
Repos: []types.Repo{},
System *SystemConfig
env *EnvConfig
}
func New() *ALRConfig {
return &ALRConfig{}
}
func readConfig(path string) (*types.Config, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
config := types.Config{}
if err := toml.NewDecoder(file).Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
func mergeStructs(dst, src interface{}) {
srcVal := reflect.ValueOf(src)
if srcVal.IsNil() {
return
}
srcVal = srcVal.Elem()
dstVal := reflect.ValueOf(dst).Elem()
for i := range srcVal.NumField() {
srcField := srcVal.Field(i)
srcFieldName := srcVal.Type().Field(i).Name
dstField := dstVal.FieldByName(srcFieldName)
if dstField.IsValid() && dstField.CanSet() {
dstField.Set(srcField)
return &ALRConfig{
System: NewSystemConfig(),
env: NewEnvConfig(),
}
}
func defaultConfigKoanf() *koanf.Koanf {
k := koanf.New(".")
defaults := map[string]interface{}{
"rootCmd": "sudo",
"useRootCmd": true,
"pagerStyle": "native",
"ignorePkgUpdates": []string{},
"logLevel": "info",
"autoPull": true,
"repos": []types.Repo{},
}
if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil {
panic(k)
}
return k
}
func (c *ALRConfig) Load() error {
systemConfig, err := readConfig(
constants.SystemConfigPath,
)
if err != nil {
slog.Debug("Cannot read system config", "err", err)
config := types.Config{}
merged := koanf.New(".")
if err := c.System.Load(); err != nil {
return fmt.Errorf("failed to load system config: %w", err)
}
config := &types.Config{}
mergeStructs(config, defaultConfig)
mergeStructs(config, systemConfig)
err = env.Parse(config)
if err != nil {
return err
if err := c.env.Load(); err != nil {
return fmt.Errorf("failed to load env config: %w", err)
}
c.cfg = config
systemK := c.System.koanf()
envK := c.env.koanf()
if err := merged.Merge(defaultConfigKoanf()); err != nil {
return fmt.Errorf("failed to merge default config: %w", err)
}
if err := merged.Merge(systemK); err != nil {
return fmt.Errorf("failed to merge system config: %w", err)
}
if err := merged.Merge(envK); err != nil {
return fmt.Errorf("failed to merge env config: %w", err)
}
if err := merged.Unmarshal("", &config); err != nil {
return fmt.Errorf("failed to unmarshal merged config: %w", err)
}
c.cfg = &config
c.paths = &Paths{}
c.paths.UserConfigPath = constants.SystemConfigPath
@ -110,52 +100,24 @@ func (c *ALRConfig) Load() error {
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
// c.initPaths()
return nil
}
func (c *ALRConfig) RootCmd() string {
return c.cfg.RootCmd
}
func (c *ALRConfig) PagerStyle() string {
return c.cfg.PagerStyle
}
func (c *ALRConfig) AutoPull() bool {
return c.cfg.AutoPull
}
func (c *ALRConfig) Repos() []types.Repo {
return c.cfg.Repos
}
func (c *ALRConfig) SetRepos(repos []types.Repo) {
c.cfg.Repos = repos
}
func (c *ALRConfig) IgnorePkgUpdates() []string {
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) LogLevel() string {
return c.cfg.LogLevel
}
func (c *ALRConfig) UseRootCmd() bool {
return c.cfg.UseRootCmd
}
func (c *ALRConfig) GetPaths() *Paths {
return c.paths
}
func (c *ALRConfig) SaveUserConfig() error {
f, err := os.Create(c.paths.UserConfigPath)
func (c *ALRConfig) ToYAML() (string, error) {
data, err := yaml.Marshal(c.cfg)
if err != nil {
return err
return "", err
}
return string(data), nil
}
return toml.NewEncoder(f).Encode(c.cfg)
}
func (c *ALRConfig) RootCmd() string { return c.cfg.RootCmd }
func (c *ALRConfig) PagerStyle() string { return c.cfg.PagerStyle }
func (c *ALRConfig) AutoPull() bool { return c.cfg.AutoPull }
func (c *ALRConfig) Repos() []types.Repo { return c.cfg.Repos }
func (c *ALRConfig) SetRepos(repos []types.Repo) { c.System.SetRepos(repos) }
func (c *ALRConfig) IgnorePkgUpdates() []string { return c.cfg.IgnorePkgUpdates }
func (c *ALRConfig) LogLevel() string { return c.cfg.LogLevel }
func (c *ALRConfig) UseRootCmd() bool { return c.cfg.UseRootCmd }
func (c *ALRConfig) GetPaths() *Paths { return c.paths }

View File

@ -0,0 +1,76 @@
// 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 config
import (
"strings"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/v2"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type EnvConfig struct {
k *koanf.Koanf
}
func NewEnvConfig() *EnvConfig {
return &EnvConfig{
k: koanf.New("."),
}
}
func (c *EnvConfig) koanf() *koanf.Koanf {
return c.k
}
func (c *EnvConfig) Load() error {
allowedKeys := map[string]struct{}{
"ALR_LOG_LEVEL": {},
"ALR_PAGER_STYLE": {},
"ALR_AUTO_PULL": {},
}
err := c.k.Load(env.Provider("ALR_", ".", func(s string) string {
_, ok := allowedKeys[s]
if !ok {
return ""
}
withoutPrefix := strings.TrimPrefix(s, "ALR_")
lowered := strings.ToLower(withoutPrefix)
dotted := strings.ReplaceAll(lowered, "__", ".")
parts := strings.Split(dotted, ".")
for i, part := range parts {
if strings.Contains(part, "_") {
parts[i] = toCamelCase(part)
}
}
return strings.Join(parts, ".")
}), nil)
return err
}
func toCamelCase(s string) string {
parts := strings.Split(s, "_")
for i := 1; i < len(parts); i++ {
if len(parts[i]) > 0 {
parts[i] = cases.Title(language.Und, cases.NoLower).String(parts[i])
}
}
return strings.Join(parts, "")
}

View File

@ -21,6 +21,7 @@ package config
// Paths contains various paths used by ALR
type Paths struct {
SystemConfigPath string
UserConfigPath string
CacheDir string
RepoDir string

View File

@ -0,0 +1,144 @@
// 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 config
import (
"encoding/json"
"errors"
"fmt"
"os"
ktoml "github.com/knadh/koanf/parsers/toml/v2"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type SystemConfig struct {
k *koanf.Koanf
cfg *types.Config
}
func NewSystemConfig() *SystemConfig {
return &SystemConfig{
k: koanf.New("."),
cfg: &types.Config{},
}
}
func (c *SystemConfig) koanf() *koanf.Koanf {
return c.k
}
func (c *SystemConfig) Load() error {
if _, err := os.Stat(constants.SystemConfigPath); errors.Is(err, os.ErrNotExist) {
return nil
}
if err := c.k.Load(file.Provider(constants.SystemConfigPath), ktoml.Parser()); err != nil {
return err
}
return c.k.Unmarshal("", c.cfg)
}
func (c *SystemConfig) Save() error {
bytes, err := c.k.Marshal(ktoml.Parser())
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
file, err := os.Create(constants.SystemConfigPath)
if err != nil {
return fmt.Errorf("failed to create config file: %w", err)
}
defer func() {
if cerr := file.Close(); cerr != nil && err == nil {
err = cerr
}
}()
if _, err := file.Write(bytes); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
if err := file.Sync(); err != nil {
return fmt.Errorf("failed to sync config: %w", err)
}
return nil
}
func (c *SystemConfig) SetRootCmd(v string) {
err := c.k.Set("rootCmd", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetUseRootCmd(v bool) {
err := c.k.Set("useRootCmd", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetPagerStyle(v string) {
err := c.k.Set("pagerStyle", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetIgnorePkgUpdates(v []string) {
err := c.k.Set("ignorePkgUpdates", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetAutoPull(v bool) {
err := c.k.Set("autoPull", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetLogLevel(v string) {
err := c.k.Set("logLevel", v)
if err != nil {
panic(err)
}
}
func (c *SystemConfig) SetRepos(v []types.Repo) {
b, err := json.Marshal(v)
if err != nil {
panic(err)
}
var m []interface{}
err = json.Unmarshal(b, &m)
if err != nil {
panic(err)
}
err = c.k.Set("repo", m)
if err != nil {
panic(err)
}
}

View File

@ -58,6 +58,50 @@ msgstr ""
msgid "Done"
msgstr ""
#: config.go:36
msgid "Manage config"
msgstr ""
#: config.go:48
msgid "Show config"
msgstr ""
#: config.go:84
msgid "Set config value"
msgstr ""
#: config.go:85
msgid "<key> <value>"
msgstr ""
#: config.go:118 config.go:126
msgid "invalid boolean value for %s: %s"
msgstr ""
#: config.go:141
msgid "use 'repo add/remove' commands to manage repositories"
msgstr ""
#: config.go:143 config.go:221
msgid "unknown config key: %s"
msgstr ""
#: config.go:147
msgid "failed to save config"
msgstr ""
#: config.go:150
msgid "Successfully set %s = %s"
msgstr ""
#: config.go:159
msgid "Get config value"
msgstr ""
#: config.go:160
msgid "<key>"
msgstr ""
#: fix.go:39
msgid "Attempt to fix problems with ALR"
msgstr ""
@ -437,11 +481,11 @@ msgstr ""
msgid "Enable interactive questions and prompts"
msgstr ""
#: main.go:146
#: main.go:147
msgid "Show help"
msgstr ""
#: main.go:150
#: main.go:151
msgid "Error while running app"
msgstr ""

View File

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-06-19 18:54+0300\n"
"PO-Revision-Date: 2025-06-29 21:05+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n"
"Language: ru\n"
@ -65,6 +65,50 @@ msgstr "Ошибка при перемещении пакета"
msgid "Done"
msgstr "Сделано"
#: config.go:36
msgid "Manage config"
msgstr "Управление конфигурацией"
#: config.go:48
msgid "Show config"
msgstr "Показать конфигурацию"
#: config.go:84
msgid "Set config value"
msgstr "Установить значение в конфигурации"
#: config.go:85
msgid "<key> <value>"
msgstr "<ключ> <значение>"
#: config.go:118 config.go:126
msgid "invalid boolean value for %s: %s"
msgstr "неверное булево значение для %s: %s"
#: config.go:141
msgid "use 'repo add/remove' commands to manage repositories"
msgstr "используйте команды 'repo add/remove' для управления репозиториями"
#: config.go:143 config.go:221
msgid "unknown config key: %s"
msgstr "неизвестный ключ конфигурации: %s"
#: config.go:147
msgid "failed to save config"
msgstr "не удалось сохранить конфигурацию"
#: config.go:150
msgid "Successfully set %s = %s"
msgstr "Успешно установлено %s = %s"
#: config.go:159
msgid "Get config value"
msgstr "Получить значение из конфигурации"
#: config.go:160
msgid "<key>"
msgstr "<ключ>"
#: fix.go:39
msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR"
@ -453,11 +497,11 @@ msgstr "Аргументы, которые будут переданы мене
msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов"
#: main.go:146
#: main.go:147
msgid "Show help"
msgstr "Показать справку"
#: main.go:150
#: main.go:151
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
@ -683,9 +727,6 @@ msgstr "Здесь нечего делать."
#~ msgid "Unable to detect user config directory"
#~ msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#~ msgid "Unable to create ALR config file"
#~ msgstr "Не удалось создать конфигурационный файл ALR"
#~ msgid "Error encoding default configuration"
#~ msgstr "Ошибка кодирования конфигурации по умолчанию"

View File

@ -83,6 +83,7 @@ func GetApp() *cli.App {
VersionCmd(),
SearchCmd(),
RepoCmd(),
ConfigCmd(),
// Internal commands
InternalBuildCmd(),
InternalInstallCmd(),

View File

@ -21,19 +21,19 @@ package types
// Config represents the ALR configuration file
type Config struct {
RootCmd string `toml:"rootCmd" env:"ALR_ROOT_CMD"`
UseRootCmd bool `toml:"useRootCmd"`
PagerStyle string `toml:"pagerStyle" env:"ALR_PAGER_STYLE"`
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
Repos []Repo `toml:"repo"`
AutoPull bool `toml:"autoPull" env:"ALR_AUTOPULL"`
LogLevel string `toml:"logLevel" env:"ALR_LOG_LEVEL"`
RootCmd string `json:"rootCmd" koanf:"rootCmd"`
UseRootCmd bool `json:"useRootCmd" koanf:"useRootCmd"`
PagerStyle string `json:"pagerStyle" koanf:"pagerStyle"`
IgnorePkgUpdates []string `json:"ignorePkgUpdates" koanf:"ignorePkgUpdates"`
Repos []Repo `json:"repo" koanf:"repo"`
AutoPull bool `json:"autoPull" koanf:"autoPull"`
LogLevel string `json:"logLevel" koanf:"logLevel"`
}
// Repo represents a ALR repo within a configuration file
type Repo struct {
Name string `toml:"name"`
URL string `toml:"url"`
Ref string `toml:"ref"`
Mirrors []string `toml:"mirrors"`
Name string `json:"name" koanf:"name"`
URL string `json:"url" koanf:"url"`
Ref string `json:"ref" koanf:"ref"`
Mirrors []string `json:"mirrors" koanf:"mirrors"`
}

14
repo.go
View File

@ -108,7 +108,7 @@ func RemoveRepoCmd() *cli.Command {
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing repo directory"), err)
}
err = cfg.SaveUserConfig()
err = cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
@ -175,7 +175,7 @@ func AddRepoCmd() *cli.Command {
})
cfg.SetRepos(reposSlice)
err = cfg.SaveUserConfig()
err = cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
@ -248,7 +248,7 @@ func SetRepoRefCmd() *cli.Command {
newRepos = append(newRepos, repo)
}
deps.Cfg.SetRepos(newRepos)
err = deps.Cfg.SaveUserConfig()
err = deps.Cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
@ -311,7 +311,7 @@ func SetUrlCmd() *cli.Command {
newRepos = append(newRepos, repo)
}
deps.Cfg.SetRepos(newRepos)
err = deps.Cfg.SaveUserConfig()
err = deps.Cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
@ -384,7 +384,7 @@ func AddMirror() *cli.Command {
}
}
deps.Cfg.SetRepos(repos)
err = deps.Cfg.SaveUserConfig()
err = deps.Cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
@ -499,7 +499,7 @@ func RemoveMirror() *cli.Command {
}
deps.Cfg.SetRepos(reposSlice)
err = deps.Cfg.SaveUserConfig()
err = deps.Cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}
@ -571,7 +571,7 @@ func ClearMirrors() *cli.Command {
reposSlice[repoIndex].Mirrors = []string{}
deps.Cfg.SetRepos(reposSlice)
err = deps.Cfg.SaveUserConfig()
err = deps.Cfg.System.Save()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error saving config"), err)
}