hyprland/module: add metainfo for modules

parent e0f51b85
package config package config
import "github.com/fatih/color" import (
"encoding/json"
"github.com/fatih/color"
)
type ItemStatus struct { type ItemStatus struct {
Symbol string Symbol string
...@@ -18,6 +22,10 @@ func (s ItemStatus) IsEqual(other ItemStatus) bool { ...@@ -18,6 +22,10 @@ func (s ItemStatus) IsEqual(other ItemStatus) bool {
return s.Label == other.Label return s.Label == other.Label
} }
func (s ItemStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(s.Label)
}
var ModuleStatus = struct { var ModuleStatus = struct {
Unknown ItemStatus Unknown ItemStatus
Enabled ItemStatus Enabled ItemStatus
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
...@@ -107,14 +108,12 @@ func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -107,14 +108,12 @@ func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error {
return err return err
} }
var msg string
action := "disable" action := "disable"
if remove { if remove {
action = "remove" action = "remove"
} }
msg, err = manager.SetModule( msg, err := manager.SetModule(
action, cmd.Args().Get(0), cmd.Bool("user"), false, action, cmd.Args().Get(0), cmd.Bool("user"), false,
) )
...@@ -169,33 +168,59 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -169,33 +168,59 @@ func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
modules := manager.GetModulesList(user, filter) modules := manager.GetModulesList(user, filter)
if len(modules) == 0 { if len(modules) == 0 {
if config.IsJSON(cmd) { if config.IsJSON(cmd) {
return ui.PrintJSON([]ui.JSONItem{}) return ui.PrintJSON([]ModuleInfoJSON{})
} }
return fmt.Errorf("нет доступных модулей") return fmt.Errorf("нет доступных модулей")
} }
items := make([]ui.TreeItem, 0, len(modules)) type moduleData struct {
info HyprModule
errorNum int
}
data := make([]moduleData, 0, len(modules))
for _, info := range modules { for _, info := range modules {
errors, err := manager.CheckModule(info.Name, user) errors, err := manager.CheckModule(info.Name, user)
if err != nil { if err != nil {
errors = []HyprConfigError{} errors = nil
}
errornum := len(errors)
desc := ""
if errornum > 0 {
desc = fmt.Sprintf("(ошибки: %d)", errornum)
} }
items = append(items, ui.TreeItem{ data = append(data, moduleData{info: info, errorNum: len(errors)})
Name: info.Name,
Status: info.Status,
Description: desc,
})
} }
if config.IsJSON(cmd) { if config.IsJSON(cmd) {
return ui.PrintJSON(ui.TreeItemsToJSON(items)) jsonItems := make([]ModuleInfoJSON, len(data))
for i, d := range data {
jsonItems[i] = ModuleInfoJSON{
Name: d.info.Name,
Status: d.info.Status.Label,
Errors: d.errorNum,
}
if d.info.Meta != nil {
jsonItems[i].Group = d.info.Meta.Group
jsonItems[i].Summary = d.info.Meta.Summary
}
}
return ui.PrintJSON(jsonItems)
}
items := make([]ui.TreeItem, 0, len(data))
for _, d := range data {
var parts []string
if d.info.Meta != nil && d.info.Meta.Summary != "" {
parts = append(parts, d.info.Meta.Summary)
}
if d.errorNum > 0 {
parts = append(parts, fmt.Sprintf("(ошибки: %d)", d.errorNum))
}
name := d.info.Name
if d.info.Meta != nil && d.info.Meta.Group != "" {
name = d.info.Meta.Group + "/" + d.info.Name
}
items = append(items, ui.TreeItem{
Name: name,
Status: d.info.Status,
Description: strings.Join(parts, " "),
})
} }
ui.RenderTree(ui.RenderTreeOptions{ ui.RenderTree(ui.RenderTreeOptions{
...@@ -268,29 +293,36 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -268,29 +293,36 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error {
jsonErrors[i] = ModuleErrorJSON{Line: e.Line, Text: e.Text} jsonErrors[i] = ModuleErrorJSON{Line: e.Line, Text: e.Text}
} }
return ui.PrintJSON(ModuleShowJSON{ return ui.PrintJSON(ModuleShowJSON{
Name: info.Name, HyprModule: info,
Status: info.Status.Label,
Path: info.Path,
ConfPath: info.ConfPath,
LineNumber: info.LineNumber,
Available: info.Available,
Errors: jsonErrors, Errors: jsonErrors,
}) })
} }
fmt.Printf("Модуль: %s\n", info.Name) blue := color.New(color.FgBlue).SprintFunc()
fmt.Printf("Статус: %s\n", info.Status.Color("%s %s", info.Status.Symbol, info.Status.Label)) fmt.Printf("%s: %s\n", blue("Модуль"), info.Name)
fmt.Printf("Путь: %s\n", info.Path) if info.Meta != nil {
fmt.Printf("Путь в конфиге: %s\n", info.ConfPath) if info.Meta.Group != "" {
fmt.Printf("Строка в конфиге: %d\n", info.LineNumber) fmt.Printf("%s: %s\n", blue("Группа"), info.Meta.Group)
}
if info.Meta.Summary != "" {
fmt.Printf("%s: %s\n", blue("Краткое"), info.Meta.Summary)
}
if info.Meta.Description != "" {
fmt.Printf("%s: \n%s\n", blue("Описание"), info.Meta.Description)
}
}
fmt.Printf("%s: %s\n", blue("Статус"), info.Status.Color("%s %s", info.Status.Symbol, info.Status.Label))
fmt.Printf("%s: %s\n", blue("Путь"), info.Path)
fmt.Printf("%s: %s\n", blue("Путь в конфиге"), info.ConfPath)
fmt.Printf("%s: %d\n", blue("Строка в конфиге"), info.LineNumber)
if info.Available { if info.Available {
fmt.Println("Доступен: да") fmt.Printf("%s: да\n", blue("Доступен"))
} else { } else {
fmt.Println("Доступен: нет") fmt.Printf("%s: нет\n", blue("Доступен"))
} }
if len(errors) > 0 { if len(errors) > 0 {
fmt.Printf("\nОшибки (%d):\n", len(errors)) fmt.Printf("\n%s (%d):\n", blue("Ошибки"), len(errors))
for _, e := range errors { for _, e := range errors {
fmt.Printf(" %d: %s\n", e.Line, e.Text) fmt.Printf(" %d: %s\n", e.Line, e.Text)
} }
......
package hyprland package hyprland
type ModuleShowJSON struct { type ModuleShowJSON struct {
Name string `json:"name"` HyprModule
Status string `json:"status"` Errors []ModuleErrorJSON `json:"errors"`
Path string `json:"path"`
ConfPath string `json:"conf_path"`
LineNumber int `json:"line_number"`
Available bool `json:"available"`
Errors []ModuleErrorJSON `json:"errors"`
} }
type ModuleErrorJSON struct { type ModuleErrorJSON struct {
Line int `json:"line"` Line int `json:"line"`
Text string `json:"text"` Text string `json:"text"`
} }
type ModuleInfoJSON struct {
Name string `json:"name"`
Group string `json:"group"`
Summary string `json:"summary"`
Status string `json:"status"`
Errors int `json:"errors"`
}
...@@ -35,12 +35,13 @@ type SourceLine struct { ...@@ -35,12 +35,13 @@ type SourceLine struct {
} }
type HyprModule struct { type HyprModule struct {
Name string Name string `json:"name"`
Status config.ItemStatus Status config.ItemStatus `json:"status"`
Path string Path string `json:"path"`
ConfPath string ConfPath string `json:"conf_path"`
LineNumber int LineNumber int `json:"line_number"`
Available bool Available bool `json:"available"`
Meta *ModuleMeta `json:"meta,omitempty"`
} }
type HyprVar struct { type HyprVar struct {
...@@ -58,7 +59,7 @@ type HyprPlugin struct { ...@@ -58,7 +59,7 @@ type HyprPlugin struct {
} }
type HyprConfigError struct { type HyprConfigError struct {
File string File string
Line int Line int
Text string Text string
} }
...@@ -200,6 +201,7 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule { ...@@ -200,6 +201,7 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule {
Path: sysFile, Path: sysFile,
ConfPath: ConfPath, ConfPath: ConfPath,
Available: Available, Available: Available,
Meta: ParseModuleMeta(sysFile),
} }
} }
return HyprModule{ return HyprModule{
...@@ -248,25 +250,26 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule { ...@@ -248,25 +250,26 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule {
ConfPath: ConfPath, ConfPath: ConfPath,
LineNumber: LineNumber, LineNumber: LineNumber,
Available: Available, Available: Available,
Meta: ParseModuleMeta(sysFile),
} }
} }
// закомментирован // закомментирован
if foundCommented { if foundCommented {
// файла нет mod := HyprModule{
Path := sysFile
if !FileExists {
Path = ""
}
// файл есть
return HyprModule{
Name: module, Name: module,
Status: config.ModuleStatus.Disabled, Status: config.ModuleStatus.Disabled,
Path: Path, Path: sysFile,
ConfPath: ConfPath, ConfPath: ConfPath,
LineNumber: LineNumber, LineNumber: LineNumber,
Available: Available, Available: Available,
} }
if FileExists {
mod.Meta = ParseModuleMeta(sysFile)
} else {
mod.Path = ""
}
return mod
} }
// файл есть, но строка отсутствует // файл есть, но строка отсутствует
...@@ -277,6 +280,7 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule { ...@@ -277,6 +280,7 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule {
Path: sysFile, Path: sysFile,
ConfPath: ConfPath, ConfPath: ConfPath,
Available: Available, Available: Available,
Meta: ParseModuleMeta(sysFile),
} }
} }
...@@ -310,6 +314,9 @@ func (m *HyprlandManager) GetModulesList(user bool, filter string) []HyprModule ...@@ -310,6 +314,9 @@ func (m *HyprlandManager) GetModulesList(user bool, filter string) []HyprModule
modules = m.SystemModules modules = m.SystemModules
} }
groupFilter := strings.TrimPrefix(filter, "group:")
isGroupFilter := groupFilter != filter
var res []HyprModule var res []HyprModule
for _, module := range modules { for _, module := range modules {
info := m.GetModuleInfo(module, user) info := m.GetModuleInfo(module, user)
...@@ -318,7 +325,9 @@ func (m *HyprlandManager) GetModulesList(user bool, filter string) []HyprModule ...@@ -318,7 +325,9 @@ func (m *HyprlandManager) GetModulesList(user bool, filter string) []HyprModule
continue continue
} }
if filter == "" || filter == "all" || info.Status.Label == filter { if filter == "" || filter == "all" ||
info.Status.Label == filter ||
(isGroupFilter && info.Meta != nil && info.Meta.Group == groupFilter) {
res = append(res, info) res = append(res, info)
} }
} }
...@@ -349,6 +358,22 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) ( ...@@ -349,6 +358,22 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) (
return "", fmt.Errorf("модуль '%s' уже включён", module) return "", fmt.Errorf("модуль '%s' уже включён", module)
} }
// отключить другие модули той же группы
var disabledGroup []string
if info.Meta != nil && info.Meta.Group != "" {
for _, other := range m.GetModulesList(user, "enabled") {
if other.Name == module {
continue
}
if other.Meta != nil && other.Meta.Group == info.Meta.Group {
m.Lines[other.LineNumber] = "#" + m.Lines[other.LineNumber]
m.updateSourceCommented(other.LineNumber, true)
m.Changed = true
disabledGroup = append(disabledGroup, other.Name)
}
}
}
// был закомментирован // был закомментирован
if info.Status.IsEqual(config.ModuleStatus.Disabled) { if info.Status.IsEqual(config.ModuleStatus.Disabled) {
if onlyNew { if onlyNew {
...@@ -357,7 +382,7 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) ( ...@@ -357,7 +382,7 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) (
m.Lines[info.LineNumber] = strings.TrimPrefix(m.Lines[info.LineNumber], "#") m.Lines[info.LineNumber] = strings.TrimPrefix(m.Lines[info.LineNumber], "#")
m.updateSourceCommented(info.LineNumber, false) m.updateSourceCommented(info.LineNumber, false)
m.Changed = true m.Changed = true
return fmt.Sprintf("Модуль '%s' включён", module), nil return m.enableMessage(module, disabledGroup), nil
} }
// не использовался // не использовался
...@@ -371,7 +396,7 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) ( ...@@ -371,7 +396,7 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) (
Commented: false, Commented: false,
}) })
m.Changed = true m.Changed = true
return fmt.Sprintf("Модуль '%s' включён", module), nil return m.enableMessage(module, disabledGroup), nil
} }
return "", fmt.Errorf("модуль '%s' не изменён", module) return "", fmt.Errorf("модуль '%s' не изменён", module)
...@@ -407,6 +432,14 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) ( ...@@ -407,6 +432,14 @@ func (m *HyprlandManager) SetModule(action, module string, user, onlyNew bool) (
return "", nil return "", nil
} }
func (m *HyprlandManager) enableMessage(module string, disabledGroup []string) string {
msg := fmt.Sprintf("Модуль '%s' включён", module)
for _, name := range disabledGroup {
msg += fmt.Sprintf("\nМодуль '%s' отключён (группа)", name)
}
return msg
}
// Plugins // Plugins
func (m *HyprlandManager) GetLoadedPlugins() []HyprPlugin { func (m *HyprlandManager) GetLoadedPlugins() []HyprPlugin {
......
package hyprland
import (
"os"
"strings"
"gopkg.in/yaml.v3"
)
const metaDelimiter = "#---"
type ModuleMeta struct {
Summary string `yaml:"summary,omitempty"`
Description string `yaml:"description,omitempty"`
Group string `yaml:"group,omitempty"`
}
func ParseModuleMeta(filePath string) *ModuleMeta {
data, err := os.ReadFile(filePath)
if err != nil {
return nil
}
lines := strings.Split(string(data), "\n")
// Найти открывающий #---
start := -1
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" {
continue
}
if trimmed == metaDelimiter {
start = i
break
}
// Первая непустая строка не #--- — метаинформации нет
return nil
}
if start < 0 {
return nil
}
// Собрать строки до закрывающего #---
var yamlLines []string
for i := start + 1; i < len(lines); i++ {
trimmed := strings.TrimSpace(lines[i])
if trimmed == metaDelimiter {
break
}
line := lines[i]
if strings.HasPrefix(line, "# ") {
line = line[2:] // снимаем "# "
} else if strings.HasPrefix(line, "#") {
line = line[1:] // снимаем "#"
}
yamlLines = append(yamlLines, line)
}
if len(yamlLines) == 0 {
return nil
}
var meta ModuleMeta
if err := yaml.Unmarshal([]byte(strings.Join(yamlLines, "\n")), &meta); err != nil {
return nil
}
if meta.Summary == "" && meta.Description == "" && meta.Group == "" {
return nil
}
return &meta
}
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