hyprland/module: add dependency system

parent f1638e59
...@@ -190,6 +190,15 @@ func HyprlandToggleModuleCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -190,6 +190,15 @@ func HyprlandToggleModuleCommand(ctx context.Context, cmd *cli.Command) error {
func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
filter := cmd.String("filter") filter := cmd.String("filter")
// Скрывать модули с отсутствующими зависимостями по умолчанию
if !strings.Contains(filter, "all") && !strings.Contains(filter, "deps:") {
if filter == "" {
filter = "deps:installed"
} else {
filter += ",deps:installed"
}
}
manager, err := GetHyprlandManager(ctx) manager, err := GetHyprlandManager(ctx)
if err != nil { if err != nil {
return err return err
...@@ -221,10 +230,11 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -221,10 +230,11 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
jsonItems := make([]ModuleInfoJSON, len(data)) jsonItems := make([]ModuleInfoJSON, len(data))
for i, d := range data { for i, d := range data {
jsonItems[i] = ModuleInfoJSON{ jsonItems[i] = ModuleInfoJSON{
Name: d.info.ShortName(), Name: d.info.ShortName(),
User: d.info.User, User: d.info.User,
Status: d.info.Status.Label, Status: d.info.Status.Label,
Errors: d.errorNum, DepsInstalled: d.info.DepsInstalled,
Errors: d.errorNum,
} }
if d.info.Meta != nil { if d.info.Meta != nil {
jsonItems[i].Group = d.info.Meta.Group jsonItems[i].Group = d.info.Meta.Group
...@@ -247,6 +257,12 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -247,6 +257,12 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
if d.info.Meta != nil && d.info.Meta.Group != "" { if d.info.Meta != nil && d.info.Meta.Group != "" {
name = d.info.Meta.Group + "/" + name name = d.info.Meta.Group + "/" + name
} }
if !d.info.DepsInstalled {
missing := missingDeps(d.info.Meta)
if len(missing) > 0 {
parts = append(parts, fmt.Sprintf("(requires: %s)", strings.Join(missing, ", ")))
}
}
item := ui.TreeItem{ item := ui.TreeItem{
Name: name, Name: name,
Status: d.info.Status, Status: d.info.Status,
...@@ -285,6 +301,15 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -285,6 +301,15 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
func HyprlandModuleListCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandModuleListCommand(ctx context.Context, cmd *cli.Command) error {
filter := cmd.String("filter") filter := cmd.String("filter")
// Скрывать модули с отсутствующими зависимостями по умолчанию
if !strings.Contains(filter, "all") && !strings.Contains(filter, "deps:") {
if filter == "" {
filter = "deps:installed"
} else {
filter += ",deps:installed"
}
}
manager, err := GetHyprlandManager(ctx) manager, err := GetHyprlandManager(ctx)
if err != nil { if err != nil {
return err return err
...@@ -381,6 +406,17 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -381,6 +406,17 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error {
} }
} }
fmt.Printf("%s: %s\n", blue(locale.T("Status")), info.Status.Color("%s %s", info.Status.Symbol, info.Status.Label)) fmt.Printf("%s: %s\n", blue(locale.T("Status")), info.Status.Color("%s %s", info.Status.Symbol, info.Status.Label))
if info.Meta != nil && len(info.Meta.Requires) > 0 {
var deps []string
for _, bin := range info.Meta.Requires {
if _, err := exec.LookPath(bin); err == nil {
deps = append(deps, color.GreenString("✓")+" "+bin)
} else {
deps = append(deps, color.RedString("✗")+" "+bin)
}
}
fmt.Printf("%s: %s\n", blue(locale.T("Dependencies")), strings.Join(deps, ", "))
}
fmt.Printf("%s: %s\n", blue(locale.T("Path")), info.Path) fmt.Printf("%s: %s\n", blue(locale.T("Path")), info.Path)
fmt.Printf("%s: %s\n", blue(locale.T("Config path")), info.ConfPath) fmt.Printf("%s: %s\n", blue(locale.T("Config path")), info.ConfPath)
fmt.Printf("%s: %d\n", blue(locale.T("Config line")), info.LineNumber) fmt.Printf("%s: %d\n", blue(locale.T("Config line")), info.LineNumber)
......
...@@ -71,21 +71,21 @@ func CommandList() *cli.Command { ...@@ -71,21 +71,21 @@ func CommandList() *cli.Command {
ArgsUsage: "module", ArgsUsage: "module",
Flags: []cli.Flag{config.FormatFlag}, Flags: []cli.Flag{config.FormatFlag},
Action: HyprlandModuleCheckCommand, Action: HyprlandModuleCheckCommand,
ShellComplete: ShellCompleteModule(""), ShellComplete: ShellCompleteModule("all"),
}, },
{ {
Name: "edit", Name: "edit",
Usage: locale.T("Edit module file"), Usage: locale.T("Edit module file"),
ArgsUsage: "module", ArgsUsage: "module",
Action: HyprlandModuleEditCommand, Action: HyprlandModuleEditCommand,
ShellComplete: ShellCompleteModule(""), ShellComplete: ShellCompleteModule("all"),
}, },
{ {
Name: "status", Name: "status",
Usage: locale.T("Hyprland module status"), Usage: locale.T("Hyprland module status"),
ArgsUsage: "module", ArgsUsage: "module",
Action: HyprlandModuleStatusCommand, Action: HyprlandModuleStatusCommand,
ShellComplete: ShellCompleteModule(""), ShellComplete: ShellCompleteModule("all"),
}, },
{ {
Name: "show", Name: "show",
...@@ -95,7 +95,7 @@ func CommandList() *cli.Command { ...@@ -95,7 +95,7 @@ func CommandList() *cli.Command {
config.FormatFlag, config.FormatFlag,
}, },
Action: HyprlandModuleShowCommand, Action: HyprlandModuleShowCommand,
ShellComplete: ShellCompleteModule(""), ShellComplete: ShellCompleteModule("all"),
}, },
{ {
Name: "list", Name: "list",
...@@ -103,7 +103,7 @@ func CommandList() *cli.Command { ...@@ -103,7 +103,7 @@ func CommandList() *cli.Command {
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "filter", Name: "filter",
Usage: locale.T("module filter (e.g. status:enabled, type:user, group:themes)"), Usage: locale.T("module filter (e.g. status:enabled, type:user, group:themes, deps:installed)"),
Aliases: []string{"f"}, Aliases: []string{"f"},
}, },
config.FormatFlag, config.FormatFlag,
...@@ -116,7 +116,7 @@ func CommandList() *cli.Command { ...@@ -116,7 +116,7 @@ func CommandList() *cli.Command {
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "filter", Name: "filter",
Usage: locale.T("module filter (e.g. status:enabled, type:user, group:themes)"), Usage: locale.T("module filter (e.g. status:enabled, type:user, group:themes, deps:installed)"),
Aliases: []string{"f"}, Aliases: []string{"f"},
}, },
config.FormatFlag, config.FormatFlag,
...@@ -128,7 +128,7 @@ func CommandList() *cli.Command { ...@@ -128,7 +128,7 @@ func CommandList() *cli.Command {
Usage: locale.T("enable module"), Usage: locale.T("enable module"),
ArgsUsage: "module", ArgsUsage: "module",
Action: HyprlandModuleEnableCommand, Action: HyprlandModuleEnableCommand,
ShellComplete: ShellCompleteModule("status:disabled"), ShellComplete: ShellCompleteModule("status:noenabled,deps:installed"),
}, },
{ {
Name: "disable", Name: "disable",
...@@ -150,7 +150,7 @@ func CommandList() *cli.Command { ...@@ -150,7 +150,7 @@ func CommandList() *cli.Command {
Usage: locale.T("toggle module"), Usage: locale.T("toggle module"),
ArgsUsage: "module", ArgsUsage: "module",
Action: HyprlandToggleModuleCommand, Action: HyprlandToggleModuleCommand,
ShellComplete: ShellCompleteModule(""), ShellComplete: ShellCompleteModule("deps:installed"),
}, },
}, },
}, },
......
...@@ -11,10 +11,11 @@ type ModuleErrorJSON struct { ...@@ -11,10 +11,11 @@ type ModuleErrorJSON struct {
} }
type ModuleInfoJSON struct { type ModuleInfoJSON struct {
Name string `json:"name"` Name string `json:"name"`
User bool `json:"user"` User bool `json:"user"`
Group string `json:"group"` Group string `json:"group"`
Summary string `json:"summary"` Summary string `json:"summary"`
Status string `json:"status"` Status string `json:"status"`
Errors int `json:"errors"` DepsInstalled bool `json:"deps_installed"`
Errors int `json:"errors"`
} }
...@@ -37,14 +37,15 @@ type SourceLine struct { ...@@ -37,14 +37,15 @@ type SourceLine struct {
} }
type HyprModule struct { type HyprModule struct {
Name string `json:"name"` Name string `json:"name"`
User bool `json:"user"` User bool `json:"user"`
Status config.ItemStatus `json:"status"` Status config.ItemStatus `json:"status"`
Path string `json:"path"` Path string `json:"path"`
ConfPath string `json:"conf_path"` ConfPath string `json:"conf_path"`
LineNumber int `json:"line_number"` LineNumber int `json:"line_number"`
Available bool `json:"available"` Available bool `json:"available"`
Meta *ModuleMeta `json:"meta,omitempty"` DepsInstalled bool `json:"deps_installed"`
Meta *ModuleMeta `json:"meta,omitempty"`
} }
func (m HyprModule) ShortName() string { func (m HyprModule) ShortName() string {
...@@ -213,14 +214,40 @@ func (m *HyprlandManager) GetModuleInfo(module string) HyprModule { ...@@ -213,14 +214,40 @@ func (m *HyprlandManager) GetModuleInfo(module string) HyprModule {
fullName, user, found := m.resolveModule(module) fullName, user, found := m.resolveModule(module)
if !found { if !found {
return HyprModule{ return HyprModule{
Name: module, Name: module,
Status: config.ModuleStatus.Unknown, Status: config.ModuleStatus.Unknown,
Available: !slices.Contains(config.HyprlandSkipModules, module), Available: !slices.Contains(config.HyprlandSkipModules, module),
DepsInstalled: true,
} }
} }
return m.getModuleInfo(fullName, user) return m.getModuleInfo(fullName, user)
} }
func checkDeps(meta *ModuleMeta) bool {
if meta == nil || len(meta.Requires) == 0 {
return true
}
for _, bin := range meta.Requires {
if _, err := exec.LookPath(bin); err != nil {
return false
}
}
return true
}
func missingDeps(meta *ModuleMeta) []string {
if meta == nil {
return nil
}
var missing []string
for _, bin := range meta.Requires {
if _, err := exec.LookPath(bin); err != nil {
missing = append(missing, bin)
}
}
return missing
}
func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule { func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule {
modulePath := m.GetModuleFile(module, user) modulePath := m.GetModuleFile(module, user)
...@@ -237,21 +264,24 @@ func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule { ...@@ -237,21 +264,24 @@ func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule {
// Конфиг Hyprland отсутствует // Конфиг Hyprland отсутствует
if !config.FileExists(config.Env.Hyprland.Config) { if !config.FileExists(config.Env.Hyprland.Config) {
if fileExists { if fileExists {
meta := ParseModuleMeta(modulePath)
return HyprModule{ return HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Unused, Status: config.ModuleStatus.Unused,
Path: modulePath, Path: modulePath,
ConfPath: confPath, ConfPath: confPath,
Available: available, Available: available,
Meta: ParseModuleMeta(modulePath), DepsInstalled: checkDeps(meta),
Meta: meta,
} }
} }
return HyprModule{ return HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Missing, Status: config.ModuleStatus.Missing,
Available: available, Available: available,
DepsInstalled: true,
} }
} }
...@@ -280,39 +310,44 @@ func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule { ...@@ -280,39 +310,44 @@ func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule {
// файла нет // файла нет
if !fileExists { if !fileExists {
return HyprModule{ return HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Missing, Status: config.ModuleStatus.Missing,
LineNumber: lineNumber, LineNumber: lineNumber,
Available: available, Available: available,
DepsInstalled: true,
} }
} }
// файл есть // файл есть
meta := ParseModuleMeta(modulePath)
return HyprModule{ return HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Enabled, Status: config.ModuleStatus.Enabled,
Path: modulePath, Path: modulePath,
ConfPath: confPath, ConfPath: confPath,
LineNumber: lineNumber, LineNumber: lineNumber,
Available: available, Available: available,
Meta: ParseModuleMeta(modulePath), DepsInstalled: checkDeps(meta),
Meta: meta,
} }
} }
// закомментирован // закомментирован
if foundCommented { if foundCommented {
mod := HyprModule{ mod := HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Disabled, Status: config.ModuleStatus.Disabled,
Path: modulePath, Path: modulePath,
ConfPath: confPath, ConfPath: confPath,
LineNumber: lineNumber, LineNumber: lineNumber,
Available: available, Available: available,
DepsInstalled: true,
} }
if fileExists { if fileExists {
mod.Meta = ParseModuleMeta(modulePath) mod.Meta = ParseModuleMeta(modulePath)
mod.DepsInstalled = checkDeps(mod.Meta)
} else { } else {
mod.Path = "" mod.Path = ""
} }
...@@ -321,23 +356,26 @@ func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule { ...@@ -321,23 +356,26 @@ func (m *HyprlandManager) getModuleInfo(module string, user bool) HyprModule {
// файл есть, но строка отсутствует // файл есть, но строка отсутствует
if fileExists { if fileExists {
meta := ParseModuleMeta(modulePath)
return HyprModule{ return HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Unused, Status: config.ModuleStatus.Unused,
Path: modulePath, Path: modulePath,
ConfPath: confPath, ConfPath: confPath,
Available: available, Available: available,
Meta: ParseModuleMeta(modulePath), DepsInstalled: checkDeps(meta),
Meta: meta,
} }
} }
// Файла нет и упоминаний нет // Файла нет и упоминаний нет
return HyprModule{ return HyprModule{
Name: module, Name: module,
User: user, User: user,
Status: config.ModuleStatus.Unknown, Status: config.ModuleStatus.Unknown,
Available: available, Available: available,
DepsInstalled: true,
} }
} }
...@@ -435,6 +473,19 @@ func matchModuleFilter(info HyprModule, filters map[string]string) bool { ...@@ -435,6 +473,19 @@ func matchModuleFilter(info HyprModule, filters map[string]string) bool {
} }
} }
if v, ok := filters["deps"]; ok {
switch v {
case "installed":
if !info.DepsInstalled {
return false
}
case "uninstalled":
if info.DepsInstalled {
return false
}
}
}
return true return true
} }
...@@ -451,6 +502,12 @@ func (m *HyprlandManager) SetModule(action, module string, onlyNew bool) (string ...@@ -451,6 +502,12 @@ func (m *HyprlandManager) SetModule(action, module string, onlyNew bool) (string
switch action { switch action {
case "enable": case "enable":
// зависимости не установлены
if !info.DepsInstalled {
return "", fmt.Errorf(locale.T("missing module dependencies: %s"),
strings.Join(missingDeps(info.Meta), ", "))
}
// нет файла // нет файла
if info.Path == "" || info.Status.IsEqual(config.ModuleStatus.Unknown) { if info.Path == "" || info.Status.IsEqual(config.ModuleStatus.Unknown) {
return "", errors.New(locale.T("cannot enable this module")) return "", errors.New(locale.T("cannot enable this module"))
......
...@@ -10,10 +10,11 @@ import ( ...@@ -10,10 +10,11 @@ import (
const metaDelimiter = "#---" const metaDelimiter = "#---"
type ModuleMeta struct { type ModuleMeta struct {
Summary string `yaml:"summary,omitempty"` Summary string `yaml:"summary,omitempty"`
Description string `yaml:"description,omitempty"` Description string `yaml:"description,omitempty"`
Group string `yaml:"group,omitempty"` Group string `yaml:"group,omitempty"`
Order int `yaml:"order,omitempty"` Order int `yaml:"order,omitempty"`
Requires []string `yaml:"requires,omitempty"`
} }
func ParseModuleMeta(filePath string) *ModuleMeta { func ParseModuleMeta(filePath string) *ModuleMeta {
......
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