mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 12:13:31 -04:00
feat(Hyprland): Introduce Lua support for Hyprland configurations
- Note: We do not convert your existing conf configs to lua. This update only reflects DMS defaults state - Updated README.md to reflect changes - Updated Keyboard shortcut support
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package luaconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var luaRequireRE = regexp.MustCompile(`(?i)\brequire\s*\(\s*["']([^"']+)["']\s*\)`)
|
||||
|
||||
func ModuleToRelPath(module string) string {
|
||||
module = strings.TrimSpace(module)
|
||||
if module == "" {
|
||||
return ""
|
||||
}
|
||||
module = strings.NewReplacer(".", string(filepath.Separator), "/", string(filepath.Separator)).Replace(module)
|
||||
return filepath.Clean(module + ".lua")
|
||||
}
|
||||
|
||||
func ModuleToPath(baseDir, module string) string {
|
||||
rel := ModuleToRelPath(module)
|
||||
if rel == "" {
|
||||
return ""
|
||||
}
|
||||
return filepath.Clean(filepath.Join(baseDir, rel))
|
||||
}
|
||||
|
||||
func Requires(line string) []string {
|
||||
line = stripLineComment(line)
|
||||
if strings.TrimSpace(line) == "" {
|
||||
return nil
|
||||
}
|
||||
matches := luaRequireRE.FindAllStringSubmatch(line, -1)
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
modules := make([]string, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
if len(match) > 1 && strings.TrimSpace(match[1]) != "" {
|
||||
modules = append(modules, strings.TrimSpace(match[1]))
|
||||
}
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
func Require(line string) (string, bool) {
|
||||
modules := Requires(line)
|
||||
if len(modules) != 1 {
|
||||
return "", false
|
||||
}
|
||||
return modules[0], true
|
||||
}
|
||||
|
||||
func RequiresTarget(filePath, targetAbs string, processed map[string]bool) bool {
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return requiresTarget(absPath, filepath.Dir(absPath), targetAbs, processed)
|
||||
}
|
||||
|
||||
func requiresTarget(filePath, rootDir, targetAbs string, processed map[string]bool) bool {
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
targetAbsClean := filepath.Clean(targetAbs)
|
||||
|
||||
if processed[absPath] {
|
||||
return false
|
||||
}
|
||||
processed[absPath] = true
|
||||
|
||||
data, err := os.ReadFile(absPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, raw := range strings.Split(string(data), "\n") {
|
||||
for _, module := range Requires(raw) {
|
||||
candidate := ModuleToPath(rootDir, module)
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
if filepath.Clean(candidate) == targetAbsClean {
|
||||
return true
|
||||
}
|
||||
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
|
||||
if requiresTarget(candidate, rootDir, targetAbs, processed) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func stripLineComment(line string) string {
|
||||
inStr := byte(0)
|
||||
esc := false
|
||||
for i := 0; i+1 < len(line); i++ {
|
||||
c := line[i]
|
||||
if inStr != 0 {
|
||||
if esc {
|
||||
esc = false
|
||||
continue
|
||||
}
|
||||
if c == '\\' && inStr == '"' {
|
||||
esc = true
|
||||
continue
|
||||
}
|
||||
if c == inStr {
|
||||
inStr = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch c {
|
||||
case '"', '\'':
|
||||
inStr = c
|
||||
case '-':
|
||||
if line[i+1] == '-' {
|
||||
return line[:i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package luaconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModuleToRelPath(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"dms.binds": filepath.Join("dms", "binds.lua"),
|
||||
"dms/binds-user": filepath.Join("dms", "binds-user.lua"),
|
||||
"awesome/anim": filepath.Join("awesome", "anim.lua"),
|
||||
"awesome.colors": filepath.Join("awesome", "colors.lua"),
|
||||
" awesome.binds ": filepath.Join("awesome", "binds.lua"),
|
||||
}
|
||||
|
||||
for input, want := range tests {
|
||||
if got := ModuleToRelPath(input); got != want {
|
||||
t.Fatalf("ModuleToRelPath(%q) = %q, want %q", input, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiresSkipsComments(t *testing.T) {
|
||||
if modules := Requires(`-- require("dms.binds")`); len(modules) != 0 {
|
||||
t.Fatalf("expected commented require to be ignored, got %#v", modules)
|
||||
}
|
||||
|
||||
modules := Requires(`print("-- not a comment") require("dms.binds") -- require("ignored")`)
|
||||
if len(modules) != 1 || modules[0] != "dms.binds" {
|
||||
t.Fatalf("unexpected modules: %#v", modules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiresTargetRecurses(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
dmsDir := filepath.Join(tmpDir, "dms")
|
||||
if err := os.MkdirAll(dmsDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target := filepath.Join(dmsDir, "windowrules.lua")
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, "hyprland.lua"), []byte(`require("dms.extra")`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dmsDir, "extra.lua"), []byte(`require("dms.windowrules")`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(target, []byte(`-- rules`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !RequiresTarget(filepath.Join(tmpDir, "hyprland.lua"), target, make(map[string]bool)) {
|
||||
t.Fatal("expected recursive require lookup to find target")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user