hyprland/modules: use HyprlandManager

parent d661cf1a
package hyprland
import (
"ximperconf/config"
"ximperconf/ui"
"ximperconf/utils"
......@@ -9,104 +8,25 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/fatih/color"
"github.com/urfave/cli/v3"
)
var (
HomeDir, _ = os.UserHomeDir()
)
type HyprConfigError struct {
File string
Line int
Text string
}
type HyprModuleInfo struct {
Status string
Available bool
LineNumber int
}
func HyprlandCheck(configPath string) ([]HyprConfigError, error) {
cmd := exec.Command(
"hyprland", "--verify-config",
"-c", configPath,
)
output, err := cmd.CombinedOutput()
func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error {
manager, err := GetHyprlandManager(ctx)
if err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return nil, err
}
}
lines := strings.Split(string(output), "\n")
results := []HyprConfigError{}
start := false
for _, line := range lines {
if strings.Contains(line, "======== Config parsing result:") {
start = true
continue
}
if !start {
continue
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
// вложенные дубли
if strings.Contains(line, ": Config error in file ") {
continue
}
prefix, msg, ok := strings.Cut(line, ": ")
if !ok {
continue
return err
}
var file string
var lineNum int
_, err := fmt.Sscanf(
prefix,
"Config error in file %s at line %d",
&file, &lineNum,
)
result, err := manager.Check(manager.ConfPath)
if err != nil {
continue
}
results = append(results, HyprConfigError{
File: file,
Line: lineNum,
Text: msg,
})
}
return results, nil
}
func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error {
h := config.Env.Hyprland
if h == nil || h.Config == "" {
fmt.Println("")
return err
}
result, err := HyprlandCheck(h.Config)
if err != nil {
color.Red(err.Error())
return err
if len(result) == 0 {
color.Green("Ошибок нет")
return nil
}
for _, e := range result {
......@@ -115,120 +35,8 @@ func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error {
return nil
}
func HyprlandGetModuleFile(module string, user bool) string {
if user {
return filepath.Join(config.Env.Hyprland.UserModulesDir, module+".conf")
}
return filepath.Join(config.Env.Hyprland.SystemModulesDir, module+".conf")
}
func hyprlandModuleInfo(module string, user bool) HyprModuleInfo {
sysFile, lineFull, lineTilde := hyprlandModuleLines(module, user)
ModuleAvailable := true
for _, skipModule := range config.HyprlandSkipModules {
if module == skipModule {
ModuleAvailable = false
}
}
// Конфиг Hyprland отсутствует
if !utils.FileExists(config.Env.Hyprland.Config) {
if utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "unused",
Available: ModuleAvailable,
}
}
return HyprModuleInfo{
Status: "missing",
}
}
f, err := os.Open(config.Env.Hyprland.Config)
if err != nil {
if utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "unused",
Available: ModuleAvailable,
}
}
return HyprModuleInfo{
Status: "missing",
}
}
defer f.Close()
reFull := regexp.MustCompile(`^\s*` + regexp.QuoteMeta(lineFull) + `(?:\s|$)`)
reTilde := regexp.MustCompile(`^\s*` + regexp.QuoteMeta(lineTilde) + `(?:\s|$)`)
foundActive := false
foundCommented := false
var LineNumber int
data, _ := os.ReadFile(config.Env.Hyprland.Config)
lines := strings.Split(string(data), "\n")
for i, line := range lines {
trim := strings.TrimSpace(line)
if trim == "#"+lineFull || trim == "# "+lineFull || trim == "#"+lineTilde || trim == "# "+lineTilde {
foundCommented = true
LineNumber = i
continue
}
if reFull.MatchString(line) || reTilde.MatchString(line) {
foundActive = true
LineNumber = i
break
}
}
// подключён, но файла нет
if foundActive && !utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "missing",
Available: ModuleAvailable,
LineNumber: LineNumber,
}
}
// подключён и файл есть
if foundActive && utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "enabled",
Available: ModuleAvailable,
LineNumber: LineNumber,
}
}
// закомментирован
if foundCommented {
return HyprModuleInfo{
Status: "disabled",
Available: ModuleAvailable,
LineNumber: LineNumber,
}
}
// файл есть, но строка отсутствует
if utils.FileExists(sysFile) && !foundActive && !foundCommented {
return HyprModuleInfo{
Status: "unused",
Available: ModuleAvailable,
}
}
// Файла нет и упоминаний нет
return HyprModuleInfo{
Status: "missing",
}
}
func hyprlandModuleStatusStruct(module string, user bool) ui.ItemStatus {
info := hyprlandModuleInfo(module, user)
func hyprlandModuleStatusStruct(manager *HyprlandManager, module string, user bool) ui.ItemStatus {
info := manager.GetModuleInfo(module, user)
switch info.Status {
case "enabled":
......@@ -244,7 +52,13 @@ func hyprlandModuleStatusStruct(module string, user bool) ui.ItemStatus {
}
func HyprlandModuleStatusCommand(ctx context.Context, cmd *cli.Command) error {
info := hyprlandModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
info := manager.GetModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
if !info.Available {
color.Red("недопустимое название для модуля.")
return nil
......@@ -253,177 +67,47 @@ func HyprlandModuleStatusCommand(ctx context.Context, cmd *cli.Command) error {
return nil
}
func HyprlandModuleCheck(module string, user bool, tmp bool) ([]HyprConfigError, error) {
if module == "" {
return nil, fmt.Errorf("укажите модуль для проверки")
}
modulefile := HyprlandGetModuleFile(module, user)
if !utils.FileExists(modulefile) {
return nil, fmt.Errorf("модуль '%s' не найден", module)
}
if tmp {
tmpfile, err := os.CreateTemp("", "ximperconf-hypr-module-check-*.conf")
if err != nil {
return nil, err
}
defer os.Remove(tmpfile.Name())
func HyprlandModuleCheckCommand(ctx context.Context, cmd *cli.Command) error {
info, _ := hyprlandVarInfo()
for name, value := range info {
fmt.Fprintf(tmpfile, "$%s = %s\n", name, value)
}
fmt.Fprintf(tmpfile, "source = %s\n", modulefile)
tmpfile.Close()
modulefile = tmpfile.Name()
if cmd.Args().Get(0) == "" {
return fmt.Errorf("укажите модуль для проверки")
}
info, err := HyprlandCheck(modulefile)
manager, err := GetHyprlandManager(ctx)
if err != nil {
return nil, err
return err
}
return info, nil
}
func HyprlandModuleCheckCommand(ctx context.Context, cmd *cli.Command) error {
moduleinfo := hyprlandModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
moduleinfo := manager.GetModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
if !moduleinfo.Available {
color.Red("недопустимое название для модуля.")
return nil
return fmt.Errorf("недопустимое название для модуля")
}
info, err := HyprlandModuleCheck(cmd.Args().Get(0), cmd.Bool("user"), true)
result, err := manager.CheckModule(cmd.Args().Get(0), cmd.Bool("user"))
if err != nil {
color.Red(err.Error())
return err
}
for _, e := range info {
fmt.Printf("%s:%d -> %s\n", e.File, e.Line, e.Text)
}
if len(result) == 0 {
color.Green("Ошибок нет")
return nil
}
func HyprlandSetModule(action string, module string, user bool, onlyNew bool) (string, error) {
sysFile, lineFull, lineTilde := hyprlandModuleLines(module, user)
info := hyprlandModuleInfo(module, user)
reFull := regexp.MustCompile(`^\s*#\s*` + regexp.QuoteMeta(lineFull) + `$`)
reTilde := regexp.MustCompile(`^\s*#\s*` + regexp.QuoteMeta(lineTilde) + `$`)
var out string
if !info.Available {
return "", fmt.Errorf("недопустимое название для модуля.")
}
if info.Status == "missing" {
return "", fmt.Errorf("модуль '%s' не найден: %s", module, sysFile)
}
data, _ := os.ReadFile(config.Env.Hyprland.Config)
lines := strings.Split(string(data), "\n")
changed := false
switch action {
case "enable":
// Уже включён
if info.Status == "enabled" {
return "", fmt.Errorf("модуль '%s' уже включён", module)
}
// Был закомментирован
if info.Status == "disabled" {
if onlyNew {
return "", fmt.Errorf("модуль '%s' уже присутствует в конфиге (закомментирован) — пропущено", module)
}
lines[info.LineNumber] = lineFull
changed = true
}
// Если модуль нигде не найден — добавляем новую строку
if info.Status == "unused" {
lines = append(lines, lineFull)
changed = true
}
if !changed {
return "", fmt.Errorf("модуль '%s' не изменён", module)
}
out = fmt.Sprintf("Модуль '%s' включён", module)
case "disable":
// Уже выключен
if info.Status == "disabled" {
return "", fmt.Errorf("модуль '%s' уже отключён", module)
}
// Включён
if info.Status == "enabled" {
lines[info.LineNumber] = "# " + lineFull
out = fmt.Sprintf("Модуль '%s' отключён", module)
changed = true
}
if !changed {
return "", fmt.Errorf("модуль '%s' не найден в конфигурации", module)
}
case "remove":
// слайс для строк, которые не нужно удалять
newLines := []string{}
for _, l := range lines {
trim := strings.TrimSpace(l)
// Проверяем все возможные варианты
isModuleLine := trim == lineFull ||
trim == lineTilde ||
reFull.MatchString(l) ||
reTilde.MatchString(l)
// Если это не модуль - добавляем в новый список
if !isModuleLine {
newLines = append(newLines, l)
} else {
changed = true
}
}
// Заменяем lines на отфильтрованные строки
if changed {
lines = newLines
}
if !changed {
return "", fmt.Errorf("модуль '%s' не найден в конфигурации", module)
}
out = fmt.Sprintf("Модуль '%s' удалён", module)
}
if changed {
_ = os.WriteFile(config.Env.Hyprland.Config, []byte(strings.Join(lines, "\n")), 0644)
for _, e := range result {
fmt.Printf("%s:%d -> %s\n", e.File, e.Line, e.Text)
}
return out, nil
return nil
}
func HyprlandModuleEnableCommand(ctx context.Context, cmd *cli.Command) error {
msg, err := HyprlandSetModule("enable", cmd.Args().Get(0), cmd.Bool("user"), false)
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
msg, err := manager.SetModule("enable", cmd.Args().Get(0), cmd.Bool("user"), false)
if err != nil {
color.Red(err.Error())
return err
}
......@@ -433,17 +117,23 @@ func HyprlandModuleEnableCommand(ctx context.Context, cmd *cli.Command) error {
func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error {
remove := cmd.Bool("remove")
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
var msg string
var err error
action := "disable"
if remove {
msg, err = HyprlandSetModule("remove", cmd.Args().Get(0), cmd.Bool("user"), false)
} else {
msg, err = HyprlandSetModule("disable", cmd.Args().Get(0), cmd.Bool("user"), false)
action = "remove"
}
msg, err = manager.SetModule(
action, cmd.Args().Get(0), cmd.Bool("user"), false,
)
if err != nil {
color.Red(err.Error())
return err
}
......@@ -453,77 +143,51 @@ func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error {
func HyprlandToggleModuleCommand(ctx context.Context, cmd *cli.Command) error {
module := cmd.Args().Get(0)
user := cmd.Bool("user")
info := hyprlandModuleInfo(module, user)
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
info := manager.GetModuleInfo(module, user)
var msg string
var err error
switch info.Status {
case "enabled":
msg, err = HyprlandSetModule("disable", module, user, false)
msg, err = manager.SetModule("disable", module, user, false)
case "disabled", "unused":
msg, err = HyprlandSetModule("enable", module, user, false)
msg, err = manager.SetModule("enable", module, user, false)
}
if err != nil {
color.Red(err.Error())
return err
}
color.Green(msg)
return nil
}
func hyprlandListModules(user bool, filter string) []string {
var dir string
if user {
dir = config.Env.Hyprland.UserModulesDir
} else {
dir = config.Env.Hyprland.SystemModulesDir
}
entries, err := os.ReadDir(dir)
if err != nil {
return nil
}
var modules []string
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".conf") {
continue
}
module := strings.TrimSuffix(e.Name(), ".conf")
info := hyprlandModuleInfo(module, user)
if !info.Available {
continue
}
if filter == "" || filter == "all" {
modules = append(modules, module)
continue
}
func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
user := cmd.Bool("user")
filter := cmd.String("filter")
if info.Status == filter {
modules = append(modules, module)
}
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
return modules
}
func hyprlandInfoModules(user bool, filter string) {
modules := hyprlandListModules(user, filter)
modules := manager.GetModulesList(user, filter)
if len(modules) == 0 {
color.Red("Нет доступных модулей")
return
return fmt.Errorf("нет доступных модулей")
}
items := make([]ui.TreeItem, 0, len(modules))
for _, module := range modules {
status := hyprlandModuleStatusStruct(module, user)
status := hyprlandModuleStatusStruct(manager, module, user)
errors, err := HyprlandModuleCheck(module, user, true)
errors, err := manager.CheckModule(module, user)
if err != nil {
errors = []HyprConfigError{}
......@@ -547,27 +211,27 @@ func hyprlandInfoModules(user bool, filter string) {
StatusMap: ui.ColorStatusMap,
Sort: true,
})
}
func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
hyprlandInfoModules(cmd.Bool("user"), cmd.String("filter"))
return nil
}
func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error {
module := cmd.Args().Get(0)
user := cmd.Bool("user")
modulefile := HyprlandGetModuleFile(module, user)
info := hyprlandModuleInfo(module, user)
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
modulefile := manager.GetModuleFile(module, user)
info := manager.GetModuleInfo(module, user)
if !info.Available {
color.Red("недопустимое название для модуля.")
return nil
return fmt.Errorf("недопустимое название для модуля")
}
if !utils.FileExists(modulefile) {
color.Red("Модуль '%s' не найден: %s", module, modulefile)
return nil
if info.Path == "" {
return fmt.Errorf("модуль '%s' не найден: %s", module, modulefile)
}
editor := utils.GetEditor()
......@@ -577,18 +241,10 @@ func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error {
editCmd.Stdout = os.Stdout
editCmd.Stderr = os.Stderr
err := editCmd.Run()
err = editCmd.Run()
if err != nil {
color.Red("Не удалось запустить редактор: %s", err.Error())
return err
return fmt.Errorf("не удалось запустить редактор: %s", err.Error())
}
return nil
}
func hyprlandModuleLines(module string, user bool) (sysFile, lineFull, lineTilde string) {
sysFile = HyprlandGetModuleFile(module, user)
lineFull = "source = " + sysFile
lineTilde = "source = ~" + strings.TrimPrefix(sysFile, HomeDir)
return
}
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