preset: refactor preset system and YAML config format

parent 5b29e43c
...@@ -3,13 +3,8 @@ package config ...@@ -3,13 +3,8 @@ package config
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"ximperconf/utils"
) )
type presetEnv struct {
Profiles map[string]PresetProfile
}
type hyprEnv struct { type hyprEnv struct {
Config string Config string
SystemModulesDir string SystemModulesDir string
...@@ -24,7 +19,7 @@ type repoEnv struct { ...@@ -24,7 +19,7 @@ type repoEnv struct {
type environment struct { type environment struct {
Version string Version string
IsRoot bool IsRoot bool
Preset *presetEnv Profiles map[string]PresetProfile
Hyprland *hyprEnv Hyprland *hyprEnv
Repo *repoEnv Repo *repoEnv
} }
...@@ -35,19 +30,14 @@ func InitConfig() error { ...@@ -35,19 +30,14 @@ func InitConfig() error {
Env.Version = "0.1.0" Env.Version = "0.1.0"
Env.IsRoot = os.Geteuid() == 0 Env.IsRoot = os.Geteuid() == 0
presetCfg, err := loadPresetConfig("/etc/ximperdistro/ximperconf/preset.d/") profiles, err := loadPresetProfiles("/etc/ximperdistro/ximperconf/preset.d/")
if err != nil { if err != nil {
// Если ошибка, пустой map Env.Profiles = map[string]PresetProfile{}
Env.Preset = &presetEnv{
Profiles: map[string]PresetProfile{},
}
} else { } else {
Env.Preset = &presetEnv{ Env.Profiles = profiles
Profiles: presetCfg.Profiles,
}
} }
if utils.FileExists("/usr/bin/hyprland") { if FileExists("/usr/bin/hyprland") {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
userModules := filepath.Join(home, ".config", "hypr") userModules := filepath.Join(home, ".config", "hypr")
systemModules := "/etc/ximperdistro/hyprland/hypr" systemModules := "/etc/ximperdistro/hyprland/hypr"
...@@ -63,14 +53,19 @@ func InitConfig() error { ...@@ -63,14 +53,19 @@ func InitConfig() error {
} }
Env.Repo = &repoEnv{ Env.Repo = &repoEnv{
DeferredInfoURL: "https://download.etersoft.ru/pub/Etersoft/Sisyphus/Deferred_Info.html", DeferredInfoURL: "https://download.etersoft.ru/pub/Ximper/Deferred_Info.html",
} }
return nil return nil
} }
func FileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func optionalPath(path string) string { func optionalPath(path string) string {
if utils.FileExists(path) { if FileExists(path) {
return path return path
} }
return "" return ""
......
...@@ -9,40 +9,39 @@ import ( ...@@ -9,40 +9,39 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type copyEntry struct { type CopyEntry struct {
Src string `yaml:"src"` Src string `yaml:"src"`
Dest string `yaml:"dest"` Dest string `yaml:"dest"`
} }
type linkEntry struct { type LinkEntry struct {
Src string `yaml:"src"`
Dest string `yaml:"dest"`
Apps []string `yaml:"apps,omitempty"` Apps []string `yaml:"apps,omitempty"`
App string `yaml:"app,omitempty"`
Sys string `yaml:"sys"`
User string `yaml:"user"`
} }
type releaseReplaceEntry struct { type ReplaceEntry struct {
Src string `yaml:"src"` Src string `yaml:"src"`
Dest string `yaml:"dest"` Dest string `yaml:"dest"`
} }
type grubEntry struct { type GrubEntry struct {
FixResolution bool `yaml:"fix-resolution,omitempty"` FixResolution bool `yaml:"fix-resolution,omitempty"`
Update bool `yaml:"update,omitempty"` Update bool `yaml:"update,omitempty"`
} }
type systemEntry struct { type SystemEntry struct {
Grub grubEntry `yaml:"grub,omitempty"` Grub GrubEntry `yaml:"grub,omitempty"`
} }
type hyprVar struct { type HyprVar struct {
Var string `yaml:"var"` Name string `yaml:"name"`
Value string `yaml:"value"` Value string `yaml:"value"`
Force bool `yaml:"force,omitempty"` Force bool `yaml:"force,omitempty"`
} }
type hyprModule struct { type HyprModule struct {
Module string `yaml:"module"` Name string `yaml:"name"`
Action string `yaml:"action"` Action string `yaml:"action"`
User bool `yaml:"user,omitempty"` User bool `yaml:"user,omitempty"`
NewOnly bool `yaml:"new-only,omitempty"` NewOnly bool `yaml:"new-only,omitempty"`
...@@ -51,35 +50,26 @@ type hyprModule struct { ...@@ -51,35 +50,26 @@ type hyprModule struct {
Init bool `yaml:"init,omitempty"` Init bool `yaml:"init,omitempty"`
} }
type hyprOptions struct { type HyprlandEntry struct {
SyncSystemLayouts bool `yaml:"sync-system-layouts,omitempty"` SyncLayouts bool `yaml:"sync-layouts,omitempty"`
} Vars []HyprVar `yaml:"vars,omitempty"`
Modules []HyprModule `yaml:"modules,omitempty"`
type hyprlandEntry struct {
Option hyprOptions `yaml:"options,omitempty"`
Vars []hyprVar `yaml:"vars,omitempty"`
Modules []hyprModule `yaml:"modules,omitempty"`
} }
type PresetProfile struct { type PresetProfile struct {
Binary string `yaml:"binary,omitempty"` Binary string `yaml:"binary,omitempty"`
Description string `yaml:"description,omitempty"` Description string `yaml:"description,omitempty"`
Root bool `yaml:"root,omitempty"` Root bool `yaml:"root,omitempty"`
Copy []copyEntry `yaml:"copy,omitempty"` Priority int `yaml:"priority,omitempty"`
Links []linkEntry `yaml:"links,omitempty"` Copy []CopyEntry `yaml:"copy,omitempty"`
ReleaseReplace []releaseReplaceEntry `yaml:"release-replace,omitempty"` Links []LinkEntry `yaml:"links,omitempty"`
System systemEntry `yaml:"system,omitempty"` Replace []ReplaceEntry `yaml:"replace,omitempty"`
System SystemEntry `yaml:"system,omitempty"`
// ----- hyprland ----- Hyprland HyprlandEntry `yaml:"hyprland,omitempty"`
Hyprland hyprlandEntry `yaml:"hyprland,omitempty"`
} }
type Presets struct { func loadPresetProfiles(dir string) (map[string]PresetProfile, error) {
Profiles map[string]PresetProfile `yaml:"profiles"` profiles := map[string]PresetProfile{}
}
func loadPresetConfig(dir string) (*Presets, error) {
cfg := &Presets{Profiles: map[string]PresetProfile{}}
entries, err := os.ReadDir(dir) entries, err := os.ReadDir(dir)
if err != nil { if err != nil {
...@@ -108,9 +98,8 @@ func loadPresetConfig(dir string) (*Presets, error) { ...@@ -108,9 +98,8 @@ func loadPresetConfig(dir string) (*Presets, error) {
} }
profileName := strings.TrimSuffix(name, filepath.Ext(name)) profileName := strings.TrimSuffix(name, filepath.Ext(name))
profiles[profileName] = p
cfg.Profiles[profileName] = p
} }
return cfg, nil return profiles, nil
} }
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"ximperconf/config" "ximperconf/config"
"ximperconf/locale" "ximperconf/locale"
"ximperconf/ui" "ximperconf/ui"
"ximperconf/utils"
"context" "context"
"errors" "errors"
...@@ -267,7 +266,7 @@ func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -267,7 +266,7 @@ func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf(locale.T("module '%s' not found: %s"), module, modulefile) return fmt.Errorf(locale.T("module '%s' not found: %s"), module, modulefile)
} }
editor := utils.GetEditor() editor := getEditor()
editCmd := exec.Command(editor, modulefile) editCmd := exec.Command(editor, modulefile)
editCmd.Stdin = os.Stdin editCmd.Stdin = os.Stdin
...@@ -344,3 +343,10 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -344,3 +343,10 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error {
return nil return nil
} }
func getEditor() string {
if editor := os.Getenv("EDITOR"); editor != "" {
return editor
}
return "micro"
}
...@@ -13,7 +13,6 @@ import ( ...@@ -13,7 +13,6 @@ import (
"time" "time"
"ximperconf/config" "ximperconf/config"
"ximperconf/locale" "ximperconf/locale"
"ximperconf/utils"
) )
type HyprlandManager struct { type HyprlandManager struct {
...@@ -193,10 +192,10 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule { ...@@ -193,10 +192,10 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule {
} }
} }
FileExists := utils.FileExists(sysFile) FileExists := config.FileExists(sysFile)
// Конфиг Hyprland отсутствует // Конфиг Hyprland отсутствует
if !utils.FileExists(config.Env.Hyprland.Config) { if !config.FileExists(config.Env.Hyprland.Config) {
if FileExists { if FileExists {
return HyprModule{ return HyprModule{
Name: module, Name: module,
...@@ -796,7 +795,7 @@ func (m *HyprlandManager) GetPluginFile(name string) (string, error) { ...@@ -796,7 +795,7 @@ func (m *HyprlandManager) GetPluginFile(name string) (string, error) {
} }
path := filepath.Join(m.PluginsDir, name+".so") path := filepath.Join(m.PluginsDir, name+".so")
if !utils.FileExists(path) { if !config.FileExists(path) {
return "", errors.New(locale.T("plugin not found")) return "", errors.New(locale.T("plugin not found"))
} }
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"ximperconf/config" "ximperconf/config"
"ximperconf/locale" "ximperconf/locale"
"ximperconf/ui" "ximperconf/ui"
"ximperconf/utils"
"fmt" "fmt"
"os" "os"
...@@ -121,7 +120,7 @@ func hyprlandVarUnset(name string) error { ...@@ -121,7 +120,7 @@ func hyprlandVarUnset(name string) error {
color.Red(locale.T("Invalid variable name: %s"), name) color.Red(locale.T("Invalid variable name: %s"), name)
os.Exit(1) os.Exit(1)
} }
if !utils.FileExists(config.Env.Hyprland.Config) { if !config.FileExists(config.Env.Hyprland.Config) {
color.Red(locale.T("Configuration not found: %s"), config.Env.Hyprland.Config) color.Red(locale.T("Configuration not found: %s"), config.Env.Hyprland.Config)
os.Exit(1) os.Exit(1)
} }
......
...@@ -7,7 +7,8 @@ hyprland/var-actions.go ...@@ -7,7 +7,8 @@ hyprland/var-actions.go
main.go main.go
preset/actions.go preset/actions.go
preset/commands.go preset/commands.go
preset/tools.go preset/helpers.go
preset/result.go
repo/actions.go repo/actions.go
repo/commands.go repo/commands.go
system/commands.go system/commands.go
......
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: ximperconf\n" "Project-Id-Version: ximperconf\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-14 18:34+0300\n" "POT-Creation-Date: 2026-02-14 21:56+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -16,6 +16,7 @@ msgstr "" ...@@ -16,6 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: hyprland/actions.go:27 #: hyprland/actions.go:27
msgid "Configuration fixed" msgid "Configuration fixed"
...@@ -229,19 +230,19 @@ msgstr "" ...@@ -229,19 +230,19 @@ msgstr ""
msgid "toggle plugin" msgid "toggle plugin"
msgstr "" msgstr ""
#: hyprland/keyboard-actions.go:42 #: hyprland/keyboard-actions.go:43
msgid "failed to get system layouts: %w" msgid "failed to get system layouts: %w"
msgstr "" msgstr ""
#: hyprland/keyboard-actions.go:49 #: hyprland/keyboard-actions.go:50
msgid "failed to update kb_layout in Hyprland: %w" msgid "failed to update kb_layout in Hyprland: %w"
msgstr "" msgstr ""
#: hyprland/keyboard-actions.go:51 #: hyprland/keyboard-actions.go:52
msgid "Layout updated!" msgid "Layout updated!"
msgstr "" msgstr ""
#: hyprland/keyboard-actions.go:53 #: hyprland/keyboard-actions.go:54
msgid "layout is already set, use '--force' for forced update" msgid "layout is already set, use '--force' for forced update"
msgstr "" msgstr ""
...@@ -282,7 +283,7 @@ msgstr "" ...@@ -282,7 +283,7 @@ msgstr ""
msgid "module '%s' is already disabled" msgid "module '%s' is already disabled"
msgstr "" msgstr ""
#: hyprland/manager.go:416 preset/actions.go:240 preset/actions.go:267 #: hyprland/manager.go:416
#, c-format #, c-format
msgid "Module '%s' disabled" msgid "Module '%s' disabled"
msgstr "" msgstr ""
...@@ -292,12 +293,12 @@ msgstr "" ...@@ -292,12 +293,12 @@ msgstr ""
msgid "module '%s' not found in config" msgid "module '%s' not found in config"
msgstr "" msgstr ""
#: hyprland/manager.go:428 preset/actions.go:242 preset/actions.go:269 #: hyprland/manager.go:428
#, c-format #, c-format
msgid "Module '%s' removed" msgid "Module '%s' removed"
msgstr "" msgstr ""
#: hyprland/manager.go:434 preset/actions.go:238 preset/actions.go:265 #: hyprland/manager.go:434
#, c-format #, c-format
msgid "Module '%s' enabled" msgid "Module '%s' enabled"
msgstr "" msgstr ""
...@@ -339,7 +340,7 @@ msgstr "" ...@@ -339,7 +340,7 @@ msgstr ""
msgid "Plugin '%s' unloaded" msgid "Plugin '%s' unloaded"
msgstr "" msgstr ""
#: hyprland/manager.go:886 hyprland/manager.go:925 hyprland/var-actions.go:117 #: hyprland/manager.go:886 hyprland/manager.go:925 hyprland/var-actions.go:116
msgid "specify variable name" msgid "specify variable name"
msgstr "" msgstr ""
...@@ -367,43 +368,43 @@ msgstr "" ...@@ -367,43 +368,43 @@ msgstr ""
msgid "VARS block created, variable '%s' set: %s" msgid "VARS block created, variable '%s' set: %s"
msgstr "" msgstr ""
#: hyprland/manager.go:935 hyprland/var-actions.go:145 #: hyprland/manager.go:935 hyprland/var-actions.go:144
#, c-format #, c-format
msgid "variable '%s' not found" msgid "variable '%s' not found"
msgstr "" msgstr ""
#: hyprland/plugin-actions.go:53 #: hyprland/plugin-actions.go:54
msgid "no available plugins" msgid "no available plugins"
msgstr "" msgstr ""
#: hyprland/plugin-actions.go:80 #: hyprland/plugin-actions.go:81
msgid "Plugins" msgid "Plugins"
msgstr "" msgstr ""
#: hyprland/var-actions.go:52 #: hyprland/var-actions.go:51
msgid "No variables in configuration" msgid "No variables in configuration"
msgstr "" msgstr ""
#: hyprland/var-actions.go:77 #: hyprland/var-actions.go:76
msgid "Vars" msgid "Vars"
msgstr "" msgstr ""
#: hyprland/var-actions.go:94 #: hyprland/var-actions.go:93
#, c-format #, c-format
msgid "variable '%s' is not set" msgid "variable '%s' is not set"
msgstr "" msgstr ""
#: hyprland/var-actions.go:121 #: hyprland/var-actions.go:120
#, c-format #, c-format
msgid "Invalid variable name: %s" msgid "Invalid variable name: %s"
msgstr "" msgstr ""
#: hyprland/var-actions.go:125 #: hyprland/var-actions.go:124
#, c-format #, c-format
msgid "Configuration not found: %s" msgid "Configuration not found: %s"
msgstr "" msgstr ""
#: hyprland/var-actions.go:149 #: hyprland/var-actions.go:148
#, c-format #, c-format
msgid "Variable '%s' removed" msgid "Variable '%s' removed"
msgstr "" msgstr ""
...@@ -416,194 +417,193 @@ msgstr "" ...@@ -416,194 +417,193 @@ msgstr ""
msgid "show help" msgid "show help"
msgstr "" msgstr ""
#: preset/actions.go:77 #: preset/actions.go:30
msgid "copy"
msgstr ""
#: preset/actions.go:40
msgid "link"
msgstr ""
#: preset/actions.go:64
msgid "replace"
msgstr ""
#: preset/actions.go:70
#, c-format #, c-format
msgid "Not found: '%s'" msgid "Not found: '%s'"
msgstr "" msgstr ""
#: preset/actions.go:85 #: preset/actions.go:75
msgid "Failed to read %s: %v" msgid "Failed to read %s: %v"
msgstr "" msgstr ""
#: preset/actions.go:93 #: preset/actions.go:79
#, c-format #, c-format
msgid "Locked: %s" msgid "Locked: %s"
msgstr "" msgstr ""
#: preset/actions.go:105 #: preset/actions.go:88
#, c-format #, c-format
msgid "Up to date: '%s'" msgid "Up to date: '%s'"
msgstr "" msgstr ""
#: preset/actions.go:120 #: preset/actions.go:97
msgid "Replace error %s: %v" msgid "Replace error %s: %v"
msgstr "" msgstr ""
#: preset/actions.go:136 preset/tools.go:45 #: preset/actions.go:111 preset/actions.go:113 preset/actions.go:119
#: preset/actions.go:123 preset/actions.go:125
msgid "system"
msgstr ""
#: preset/actions.go:119
msgid "GRUB error: %v"
msgstr ""
#: preset/actions.go:123 system/grub-actions.go:86
msgid "GRUB_GFXMODE fixed"
msgstr ""
#: preset/actions.go:125 system/grub-actions.go:89
msgid "update-grub completed"
msgstr ""
#: preset/actions.go:133 preset/actions.go:139 preset/actions.go:144
#: preset/actions.go:146
msgid "var"
msgstr ""
#: preset/actions.go:133
#, c-format #, c-format
msgid "Already exists: %s" msgid "Already exists: %s"
msgstr "" msgstr ""
#: preset/actions.go:153 #: preset/actions.go:144
msgid "Error setting %s: %v" msgid "Error setting %s: %v"
msgstr "" msgstr ""
#: preset/actions.go:170 #: preset/actions.go:154 preset/actions.go:159 preset/actions.go:167
msgid "Skipped module without name" #: preset/actions.go:173 preset/actions.go:181 preset/actions.go:184
#: preset/actions.go:190 preset/actions.go:195 preset/actions.go:197
msgid "module"
msgstr "" msgstr ""
#: preset/actions.go:178 #: preset/actions.go:154
#, c-format msgid "Skipped module without name"
msgid "Module '%s': unsupported action"
msgstr "" msgstr ""
#: preset/actions.go:190 #: preset/actions.go:159
#, c-format #, c-format
msgid "Module '%s': command '%s' failed" msgid "Module '%s': unsupported action"
msgstr "" msgstr ""
#: preset/actions.go:198 #: preset/actions.go:167
#, c-format #, c-format
msgid "Module '%s': command '%s' returned no data" msgid "Module '%s': condition not met"
msgstr "" msgstr ""
#: preset/actions.go:208 #: preset/actions.go:173
#, c-format #, c-format
msgid "Module '%s': binary '%s' not found" msgid "Module '%s': binary '%s' not found"
msgstr "" msgstr ""
#: preset/actions.go:220 preset/actions.go:226 #: preset/actions.go:181 preset/actions.go:184
#, c-format #, c-format
msgid "Created empty module: %s" msgid "Created empty module: %s"
msgstr "" msgstr ""
#: preset/actions.go:258 #: preset/actions.go:195
msgid "Error (%s): %v" msgid "Error (%s): %v"
msgstr "" msgstr ""
#: preset/actions.go:286 #: preset/actions.go:208 preset/actions.go:214 preset/actions.go:222
msgid "Sync Hyprland layouts" #: preset/actions.go:225 preset/actions.go:227
msgstr "" msgid "layout"
#: preset/actions.go:295
msgid "Failed to get system layouts"
msgstr "" msgstr ""
#: preset/actions.go:308 #: preset/actions.go:208
#, c-format msgid "Sync Hyprland layouts"
msgid "XCB layout: %s"
msgstr "" msgstr ""
#: preset/actions.go:312 #: preset/actions.go:214
#, c-format msgid "Failed to get system layouts"
msgid "Hyprland layout: %s"
msgstr "" msgstr ""
#: preset/actions.go:319 #: preset/actions.go:222
msgid "Failed to update kb_layout" msgid "Failed to update kb_layout"
msgstr "" msgstr ""
#: preset/actions.go:325 #: preset/actions.go:225
#, c-format #, c-format
msgid "Layout updated: %s" msgid "Layout updated: %s"
msgstr "" msgstr ""
#: preset/actions.go:330 #: preset/actions.go:227
#, c-format #, c-format
msgid "Layout already set: %s" msgid "Layout already set: %s"
msgstr "" msgstr ""
#: preset/actions.go:354 #: preset/actions.go:270
msgid "GRUB error: %v"
msgstr ""
#: preset/actions.go:359 system/grub-actions.go:86
msgid "GRUB_GFXMODE fixed"
msgstr ""
#: preset/actions.go:364 system/grub-actions.go:89
msgid "update-grub completed"
msgstr ""
#: preset/actions.go:392
#, c-format #, c-format
msgid "Creating profile: %s" msgid "Profile %s not found"
msgstr "" msgstr ""
#: preset/actions.go:394 #: preset/actions.go:275
#, c-format #, c-format
msgid "Profile %s unavailable" msgid "Profile %s unavailable"
msgstr "" msgstr ""
#: preset/actions.go:408 preset/actions.go:468 #: preset/actions.go:287 preset/actions.go:338
msgid "no available profiles" #, c-format
msgid "Applying profile: %s"
msgstr "" msgstr ""
#: preset/actions.go:430 #: preset/actions.go:321 preset/helpers.go:73
msgid "Profiles" msgid "no available profiles"
msgstr "" msgstr ""
#: preset/actions.go:451 #: preset/commands.go:13
#, c-format msgid "Show what would be created without making changes"
msgid "Profile %s not found"
msgstr "" msgstr ""
#: preset/actions.go:472 #: preset/commands.go:18
msgid "Creating all available profiles..." msgid "Force overwrite existing files (backup to .bak)"
msgstr "" msgstr ""
#: preset/commands.go:15 #: preset/commands.go:26
msgid "Manage preset configuration profiles" msgid "Manage preset configuration profiles"
msgstr "" msgstr ""
#: preset/commands.go:19 #: preset/commands.go:30
msgid "Show information about preset profiles" msgid "Show information about preset profiles"
msgstr "" msgstr ""
#: preset/commands.go:27 #: preset/commands.go:38
msgid "Apply a profile" msgid "Apply a profile"
msgstr "" msgstr ""
#: preset/commands.go:31 preset/commands.go:45 #: preset/commands.go:45
msgid "Show what would be created without making changes"
msgstr ""
#: preset/commands.go:41
msgid "Apply all available profiles" msgid "Apply all available profiles"
msgstr "" msgstr ""
#: preset/tools.go:51 #: preset/helpers.go:93
#, c-format msgid "Profiles"
msgid "Error: %s"
msgstr ""
#: preset/tools.go:85
msgid "Copying"
msgstr ""
#: preset/tools.go:86
msgid "Creating links"
msgstr ""
#: preset/tools.go:87
msgid "Updating"
msgstr ""
#: preset/tools.go:88
msgid "Configuring system"
msgstr "" msgstr ""
#: preset/tools.go:89 #: preset/result.go:74
msgid "Creating Hyprland variables" msgid "done"
msgstr "" msgstr ""
#: preset/tools.go:90 #: preset/result.go:77
msgid "Enabling Hyprland modules" msgid "skipped"
msgstr "" msgstr ""
#: preset/tools.go:91 #: preset/result.go:80
msgid "Syncing Hyprland layout" msgid "error"
msgstr "" msgid_plural "errors"
msgstr[0] ""
msgstr[1] ""
#: repo/actions.go:31 repo/actions.go:38 #: repo/actions.go:31 repo/actions.go:38
msgid "Error fetching data" msgid "Error fetching data"
......
package preset package preset
import ( import (
"context"
"fmt"
"ximperconf/config" "ximperconf/config"
"ximperconf/locale" "ximperconf/locale"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var presetFlags = []cli.Flag{
&cli.BoolFlag{
Name: "dry-run",
Usage: locale.T("Show what would be created without making changes"),
Aliases: []string{"d"},
},
&cli.BoolFlag{
Name: "force",
Usage: locale.T("Force overwrite existing files (backup to .bak)"),
Aliases: []string{"f"},
},
}
func CommandList() *cli.Command { func CommandList() *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "preset", Name: "preset",
...@@ -23,46 +34,18 @@ func CommandList() *cli.Command { ...@@ -23,46 +34,18 @@ func CommandList() *cli.Command {
Action: presetInfoCommand, Action: presetInfoCommand,
}, },
{ {
Name: "apply", Name: "apply",
Usage: locale.T("Apply a profile"), Usage: locale.T("Apply a profile"),
Flags: []cli.Flag{ Flags: presetFlags,
&cli.BoolFlag{
Name: "dry-run",
Usage: locale.T("Show what would be created without making changes"),
Aliases: []string{"d"},
Value: false,
},
},
Action: presetApplyCommand, Action: presetApplyCommand,
ShellComplete: ShellCompleteProfiles, ShellComplete: ShellCompleteProfiles,
}, },
{ {
Name: "apply-all", Name: "apply-all",
Usage: locale.T("Apply all available profiles"), Usage: locale.T("Apply all available profiles"),
Flags: []cli.Flag{ Flags: presetFlags,
&cli.BoolFlag{
Name: "dry-run",
Usage: locale.T("Show what would be created without making changes"),
Aliases: []string{"d"},
Value: false,
},
},
Action: presetApplyAllCommand, Action: presetApplyAllCommand,
}, },
}, },
} }
} }
func ShellCompleteProfiles(ctx context.Context, cmd *cli.Command) {
if cmd.NArg() > 0 {
return
}
profiles := config.Env.Preset.Profiles
for profileName, profile := range profiles {
if profileAvailable(profile) {
fmt.Println(profileName)
}
}
}
...@@ -2,67 +2,35 @@ package preset ...@@ -2,67 +2,35 @@ package preset
import ( import (
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"ximperconf/config" "ximperconf/config"
) )
type opKind string
const (
OpCopy opKind = "copy"
OpLink opKind = "link"
OpReplace opKind = "replace"
)
type opOptions struct { type opOptions struct {
DryRun bool DryRun bool
Force bool Force bool
} }
type opResult struct { func execCopy(src, dest string, opts opOptions) (config.ItemStatus, string) {
Kind opKind
Status config.ItemStatus
Source string
Target string
Reason string
}
func commandExists(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}
// ===== Copy =====
func copyIfMissing(src, dest string, opts opOptions) opResult {
info, err := os.Stat(src) info, err := os.Stat(src)
if err != nil { if err != nil {
return opResult{ return config.OpStatus.Error, err.Error()
Kind: OpCopy,
Status: config.OpStatus.Error,
Source: src,
Target: dest,
Reason: err.Error(),
}
} }
exists := false
if _, err := os.Stat(dest); err == nil { if _, err := os.Stat(dest); err == nil {
return opResult{ if !opts.Force {
Kind: OpCopy, return config.OpStatus.Skipped, dest + " (exists)"
Status: config.OpStatus.Skipped, }
Target: dest, exists = true
Reason: "already exists", if !opts.DryRun {
_ = os.Rename(dest, dest+".bak")
} }
} }
if opts.DryRun { if opts.DryRun {
return opResult{ return config.OpStatus.DryRun, src + " → " + dest
Kind: OpCopy,
Status: config.OpStatus.DryRun,
Source: src,
Target: dest,
}
} }
if info.IsDir() { if info.IsDir() {
...@@ -72,21 +40,49 @@ func copyIfMissing(src, dest string, opts opOptions) opResult { ...@@ -72,21 +40,49 @@ func copyIfMissing(src, dest string, opts opOptions) opResult {
} }
if err != nil { if err != nil {
return opResult{ return config.OpStatus.Error, err.Error()
Kind: OpCopy, }
Status: config.OpStatus.Error,
Source: src, msg := src + " → " + dest
Target: dest, if exists {
Reason: err.Error(), msg += " (overwritten)"
}
return config.OpStatus.Done, msg
}
func execLink(src, dest string, opts opOptions) (config.ItemStatus, string) {
if _, err := os.Stat(src); os.IsNotExist(err) {
return config.OpStatus.Skipped, src + " → " + dest + " (source missing)"
}
exists := false
if _, err := os.Lstat(dest); err == nil {
if !opts.Force {
return config.OpStatus.Skipped, dest + " (exists)"
}
exists = true
if !opts.DryRun {
_ = os.Rename(dest, dest+".bak")
} }
} }
return opResult{ if opts.DryRun {
Kind: OpCopy, return config.OpStatus.DryRun, src + " → " + dest
Status: config.OpStatus.Done, }
Source: src,
Target: dest, if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return config.OpStatus.Error, err.Error()
}
if err := os.Symlink(src, dest); err != nil {
return config.OpStatus.Error, err.Error()
} }
msg := src + " → " + dest
if exists {
msg += " (overwritten)"
}
return config.OpStatus.Done, msg
} }
func copyFile(src, dest string) error { func copyFile(src, dest string) error {
...@@ -121,60 +117,8 @@ func copyDir(src, dest string) error { ...@@ -121,60 +117,8 @@ func copyDir(src, dest string) error {
}) })
} }
// ===== Link =====
func createLinkIfMissing(src, dest string, opts opOptions) opResult {
if _, err := os.Stat(src); os.IsNotExist(err) {
return opResult{
Kind: OpLink,
Status: config.OpStatus.Skipped,
Target: dest,
Reason: "source missing",
}
}
if _, err := os.Lstat(dest); err == nil {
return opResult{
Kind: OpLink,
Status: config.OpStatus.Skipped,
Target: dest,
Reason: "already exists",
}
}
if opts.DryRun {
return opResult{
Kind: OpLink,
Status: config.OpStatus.DryRun,
Source: src,
Target: dest,
}
}
if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
return opResult{
Kind: OpLink,
Status: config.OpStatus.Error,
Reason: err.Error(),
}
}
if err := os.Symlink(src, dest); err != nil {
return opResult{
Kind: OpLink,
Status: config.OpStatus.Error,
Reason: err.Error(),
}
}
return opResult{
Kind: OpLink,
Status: config.OpStatus.Done,
Source: src,
Target: dest,
}
}
// ===== Versions ===== // ===== Versions =====
func getSystemVersion() string { func getSystemVersion() string {
data, err := os.ReadFile("/etc/os-release") data, err := os.ReadFile("/etc/os-release")
if err != nil { if err != nil {
......
package preset package preset
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"ximperconf/config" "ximperconf/config"
"ximperconf/locale" "ximperconf/locale"
"ximperconf/ui" "ximperconf/ui"
"github.com/urfave/cli/v3"
) )
func profileAvailable(p config.PresetProfile) bool { func profileAvailable(p config.PresetProfile) bool {
allowed := false if p.Binary != "" {
if _, err := exec.LookPath(p.Binary); err != nil {
if p.Binary == "" { return false
allowed = true }
} else if _, err := exec.LookPath(p.Binary); err == nil {
allowed = true
} }
if p.Root != config.Env.IsRoot { if p.Root != config.Env.IsRoot {
allowed = false return false
} }
return allowed return true
} }
func profileStatus(p config.PresetProfile) config.ItemStatus { func profileStatus(p config.PresetProfile) config.ItemStatus {
...@@ -35,28 +35,9 @@ func profileStatus(p config.PresetProfile) config.ItemStatus { ...@@ -35,28 +35,9 @@ func profileStatus(p config.PresetProfile) config.ItemStatus {
return config.ProfileStatus.Unavailable return config.ProfileStatus.Unavailable
} }
func AppendOpResult(res *Result, r opResult) { func commandExists(name string) bool {
item := ui.TreeItem{ _, err := exec.LookPath(name)
Status: r.Status, return err == nil
}
switch {
case r.Status.IsEqual(config.OpStatus.Skipped):
item.Name = fmt.Sprintf(locale.T("Already exists: %s"), r.Target)
case r.Status.IsEqual(config.OpStatus.DryRun):
item.Name = fmt.Sprintf("%s → %s", r.Source, r.Target)
case r.Status.IsEqual(config.OpStatus.Done):
item.Name = fmt.Sprintf("%s → %s", r.Source, r.Target)
case r.Status.IsEqual(config.OpStatus.Error):
item.Name = fmt.Sprintf(locale.T("Error: %s"), r.Reason)
}
switch r.Kind {
case OpCopy:
res.Copies = append(res.Copies, item)
case OpLink:
res.Links = append(res.Links, item)
}
} }
func expandPath(path, name string) string { func expandPath(path, name string) string {
...@@ -81,12 +62,50 @@ func expandPath(path, name string) string { ...@@ -81,12 +62,50 @@ func expandPath(path, name string) string {
return path return path
} }
func renderPresetResult(res *Result) { func ShowProfilesInfo(jsonFormat bool) {
ui.RenderTreeItems(locale.T("Copying"), res.Copies) profiles := config.Env.Profiles
ui.RenderTreeItems(locale.T("Creating links"), res.Links)
ui.RenderTreeItems(locale.T("Updating"), res.Replaced) if len(profiles) == 0 {
ui.RenderTreeItems(locale.T("Configuring system"), res.System) if jsonFormat {
ui.RenderTreeItems(locale.T("Creating Hyprland variables"), res.HyprVars) ui.PrintJSON([]ui.JSONItem{})
ui.RenderTreeItems(locale.T("Enabling Hyprland modules"), res.HyprModules) return
ui.RenderTreeItems(locale.T("Syncing Hyprland layout"), res.SyncSystemLayouts) }
fmt.Println(config.OpStatus.Error.Color(locale.T("no available profiles")))
return
}
items := make([]ui.TreeItem, 0, len(profiles))
for name, profile := range profiles {
items = append(items, ui.TreeItem{
Name: name,
Status: profileStatus(profile),
Description: profile.Description,
})
}
if jsonFormat {
ui.PrintJSON(ui.TreeItemsToJSON(items))
return
}
ui.RenderTree(ui.RenderTreeOptions{
Title: locale.T("Profiles"),
Items: items,
Style: ui.DefaultTreeStyle,
Color: true,
Sort: true,
})
}
func ShellCompleteProfiles(ctx context.Context, cmd *cli.Command) {
if cmd.NArg() > 0 {
return
}
for name, profile := range config.Env.Profiles {
if profileAvailable(profile) {
fmt.Println(name)
}
}
} }
package preset package preset
import "ximperconf/ui" import (
"fmt"
"ximperconf/config"
"ximperconf/locale"
)
type LogEntry struct {
Category string
Name string
Status config.ItemStatus
}
type Result struct { type Result struct {
Copies []ui.TreeItem entries []LogEntry
Links []ui.TreeItem }
Replaced []ui.TreeItem
System []ui.TreeItem func (r *Result) Add(category, name string, status config.ItemStatus) {
HyprVars []ui.TreeItem r.entries = append(r.entries, LogEntry{
HyprModules []ui.TreeItem Category: category,
SyncSystemLayouts []ui.TreeItem Name: name,
Status: status,
})
}
func (r *Result) Render() {
if len(r.entries) == 0 {
return
}
maxCat := 0
for _, e := range r.entries {
if len([]rune(e.Category)) > maxCat {
maxCat = len([]rune(e.Category))
}
}
for _, e := range r.entries {
symbol := e.Status.Symbol
cat := fmt.Sprintf("%-*s", maxCat, e.Category)
if e.Status.Color != nil {
symbol = e.Status.Color(symbol)
cat = e.Status.Color(cat)
}
fmt.Printf(" %s %s %s\n", symbol, cat, e.Name)
}
summary := r.Summary()
if summary != "" {
fmt.Println()
fmt.Printf(" %s\n", summary)
}
}
func (r *Result) Summary() string {
var done, skipped, errors int
for _, e := range r.entries {
switch {
case e.Status.IsEqual(config.OpStatus.Done):
done++
case e.Status.IsEqual(config.OpStatus.Skipped):
skipped++
case e.Status.IsEqual(config.OpStatus.Error):
errors++
case e.Status.IsEqual(config.OpStatus.DryRun):
done++
}
}
parts := []string{}
if done > 0 {
parts = append(parts, fmt.Sprintf("%d %s", done, locale.T("done")))
}
if skipped > 0 {
parts = append(parts, fmt.Sprintf("%d %s", skipped, locale.T("skipped")))
}
if errors > 0 {
parts = append(parts, fmt.Sprintf("%d %s", errors, locale.TN("error", "errors", errors)))
}
if len(parts) == 0 {
return ""
}
result := parts[0]
for i := 1; i < len(parts); i++ {
result += ", " + parts[i]
}
return result
} }
package ui
import (
"github.com/fatih/color"
)
func RenderTreeLines(title string, lines []string) {
if len(lines) == 0 {
return
}
color.Blue("%s", title)
items := make([]TreeItem, len(lines))
for i, line := range lines {
items[i] = TreeItem{
Name: line,
}
}
RenderTree(RenderTreeOptions{
Items: items,
Style: DefaultTreeStyle,
Color: false,
})
}
func RenderTreeItems(title string, items []TreeItem) {
if len(items) == 0 {
return
}
color.Blue("%s", title)
RenderTree(RenderTreeOptions{
Items: items,
Style: DefaultTreeStyle,
Color: true,
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment