├── LICENSE ├── README.md ├── file.go ├── go.mod ├── hyprlang_parser.go ├── hyprlang_parser_test.go ├── line.go ├── parser.go ├── test_config ├── example.conf └── test.conf └── utils.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Hadi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyprlang Parser 2 | 3 |
8 | 9 | A Golang implementation library for the hypr config language. 10 | 11 | ## Installation 12 | 13 | ```bash 14 | go get https://github.com/anotherhadi/hyprlang-parser@latest 15 | ``` 16 | 17 | ## Functions 18 | 19 | **Understanding Section and Variable Parameters:** 20 | 21 | For most functions, you'll need to provide a "section" and a "variable" parameter. 22 | 23 | - The "section" represents the name of the section, which is separated by forward slashes (/). 24 | - The main section can be represented by either an empty string ("") or a single forward slash ("/"). 25 | - A decoration section could be "decoration" (or "/decoration/", as per your preference). 26 | 27 | So, for example, to retrieve the enabled variable in the decoration section for blur: 28 | 29 | - Set `section="decoration/blur"` 30 | - Set `variable="enabled"` 31 | 32 | --- 33 | 34 | | Function | Explanation | Example | 35 | |------------------|------------------------------------------------|-------------------------------------------------| 36 | | LoadConfig | Loads a configuration file at the specified path and returns a Config struct. It also adds the sourced configuration files. | ```config, err := LoadConfig("~/.config/hypr/hyprland.conf")``` | 37 | | WriteConfig | Writes/saves changed configurations. | ```err := config.WriteConfig()``` | 38 | | GetFirst | Returns the first value for the specified variable in the given section. | ```value := config.GetFirst("input", "kb_layout")``` | 39 | | GetAll | Returns all the values for the specified variable in the given section. | ```values := config.GetAll("", "exec-once")``` | 40 | | EditFirst | Changes the value of the first occurrence of the specified variable in the given section to the provided value. Returns an error if the variable was not found. | ```err := config.EditFirst("input/touchpad", "natural_scroll", "true")``` | 41 | | EditN | Changes the value of the nth occurrence of the specified variable in the given section to the provided value. Returns an error if the variable was not found. | ```err := config.EditN("animations", "bezier", "winIn, 0.05, 0.9, 0.1, 1.1", 2)``` | 42 | | Add | Creates a new variable with the provided value in the specified section. It creates the sections if they don't exist. If sections are not found, it will add the sections and the variable to the first config file. | ```config.Add("decoration/blur", "size", "3")``` | 43 | | RemoveFirst | Removes the first occurrence of the specified variable in the given section. Returns an error if the variable is not found. | ```err := config.RemoveFirst("", "exec-once")``` | 44 | | RemoveN | Removes the nth occurrence of the specified variable in the given section. Returns an error if the variable is not found. | ```err := config.RemoveN("", "exec-once", 2)``` | 45 | 46 | ## Example: 47 | 48 | You can find more examples and usage in [`hyprlang_parser_test.go`](hyprlang_parser_test.go). 49 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package hyprlang_parser 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func readFile(filename string) (content []string, err error) { 10 | file, err := os.OpenFile(filename, os.O_RDWR, 0644) 11 | if err != nil { 12 | return nil, err 13 | } 14 | defer file.Close() 15 | 16 | scanner := bufio.NewScanner(file) 17 | for scanner.Scan() { 18 | line := scanner.Text() 19 | content = append(content, line) 20 | } 21 | 22 | if err := scanner.Err(); err != nil { 23 | return nil, err 24 | } 25 | 26 | return content, nil 27 | } 28 | 29 | func (cf *configFile) writeFile() error { 30 | file, err := os.OpenFile(cf.path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 31 | if err != nil { 32 | return err 33 | } 34 | defer file.Close() 35 | 36 | writer := bufio.NewWriter(file) 37 | for _, line := range cf.content { 38 | _, err := fmt.Fprintln(writer, line) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | 44 | return writer.Flush() 45 | } 46 | 47 | func (c *Config) addConfigFile(path string) error { 48 | var err error 49 | configFile := configFile{ 50 | path: path, 51 | } 52 | configFile.content, err = readFile(path) 53 | if err != nil { 54 | return err 55 | } 56 | c.configFiles = append(c.configFiles, configFile) 57 | sources := c.GetAll("", "source") 58 | for _, source := range sources { 59 | alreadyAdded := false 60 | for _, conf := range c.configFiles { 61 | if source == conf.path { 62 | alreadyAdded = true 63 | } 64 | } 65 | if alreadyAdded { 66 | continue 67 | } 68 | err = c.addConfigFile(source) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/anotherhadi/hyprlang-parser 2 | 3 | go 1.21.5 4 | -------------------------------------------------------------------------------- /hyprlang_parser.go: -------------------------------------------------------------------------------- 1 | package hyprlang_parser 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type configFile struct { 8 | path string // Path of the file 9 | content []string // Content, line by line, of the file 10 | changed bool // Store if config has changed 11 | } 12 | 13 | type Config struct { 14 | configFiles []configFile // List of config files (sources included) 15 | indentation int // Indentation of the config file, based on the first section of the entry file 16 | } 17 | 18 | // LoadConfig loads config at {configPath} and returns a Config struct 19 | // It also adds the sourced configuration files 20 | func LoadConfig(configPath string) (config Config, err error) { 21 | err = config.addConfigFile(configPath) 22 | config.indentation = getIndentation(&config.configFiles[0].content) 23 | return 24 | } 25 | 26 | // WriteConfig writes/saves changed configs 27 | func (c *Config) WriteConfig() error { 28 | for i, configFile := range c.configFiles { 29 | if configFile.changed { 30 | err := configFile.writeFile() 31 | if err != nil { 32 | return err 33 | } 34 | c.configFiles[i].changed = false 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | // GetFirst returns the first value for {variable} at {section} 41 | func (c *Config) GetFirst(section, variable string) string { 42 | var value string 43 | for _, configFile := range c.configFiles { 44 | values := getVariables(configFile.content, parseSectionString(section), variable, -1) 45 | if len(values) > 0 { 46 | return values[0] 47 | } 48 | } 49 | return value 50 | } 51 | 52 | // GetAll returns all the values for {variable} at {section} 53 | func (c *Config) GetAll(section, variable string) []string { 54 | var values []string 55 | for _, configFile := range c.configFiles { 56 | values = append(values, getVariables(configFile.content, parseSectionString(section), variable, 0)...) 57 | } 58 | return values 59 | } 60 | 61 | // EditFirst changes the value of the first {variable} at {section} to {value} 62 | // It returns an error if the variable was not found 63 | func (c *Config) EditFirst(section, variable, value string) error { 64 | var isEdited bool 65 | for i, configFile := range c.configFiles { 66 | isEdited = editVariableN(&configFile.content, parseSectionString(section), variable, value, 0, c.indentation) 67 | if isEdited { 68 | c.configFiles[i].changed = true 69 | return nil 70 | } 71 | } 72 | 73 | return errors.New("Variable not found (Not edited).") 74 | } 75 | 76 | // EditN changes the value of the {n} {variable} at {section} to {value} 77 | // It returns an error if the variable was not found 78 | func (c *Config) EditN(section, variable, value string, n int) error { 79 | var isEdited bool 80 | for i, configFile := range c.configFiles { 81 | isEdited = editVariableN(&configFile.content, parseSectionString(section), variable, value, n, c.indentation) 82 | if isEdited { 83 | c.configFiles[i].changed = true 84 | return nil 85 | } 86 | } 87 | 88 | return errors.New("Variable not found (Not edited).") 89 | } 90 | 91 | // Add creates a {variable} at {section} with the {value} value 92 | // It creates the sections if they don't exist 93 | // If sections are not found, it will add the sections and the variable to the first config file 94 | func (c *Config) Add(section, variable, value string) { 95 | var whereSectionExist int 96 | var exist bool = false 97 | 98 | for i, configFile := range c.configFiles { 99 | exist = doesSectionExist(&configFile.content, parseSectionString(section)) 100 | if exist { 101 | whereSectionExist = i 102 | break 103 | } 104 | } 105 | 106 | if exist { 107 | addVariable(&c.configFiles[whereSectionExist].content, parseSectionString(section), variable, value, c.indentation) 108 | c.configFiles[whereSectionExist].changed = true 109 | } else { 110 | parsedSection := parseSectionString(section) 111 | for i := 0; i < len(parsedSection); i++ { 112 | if !doesSectionExist(&c.configFiles[0].content, parsedSection[:i+1]) { 113 | addSection(&c.configFiles[0].content, parsedSection[:i], parsedSection[i], c.indentation) 114 | } 115 | } 116 | addVariable(&c.configFiles[0].content, parsedSection, variable, value, c.indentation) 117 | c.configFiles[0].changed = true 118 | } 119 | } 120 | 121 | // RemoveFirst removes the first {variable} at {section} 122 | // It returns an error if the variable is not found 123 | func (c *Config) RemoveFirst(section, variable string) error { 124 | var isRemoved bool 125 | for i, configFile := range c.configFiles { 126 | isRemoved = removeVariableN(&configFile.content, parseSectionString(section), variable, 0) 127 | if isRemoved { 128 | c.configFiles[i].changed = true 129 | return nil 130 | } 131 | } 132 | return errors.New("Variable not found (Not removed).") 133 | } 134 | 135 | // RemoveN removes the {n} {variable} at {section} 136 | // It returns an error if the variable is not found 137 | func (c *Config) RemoveN(section, variable string, n int) error { 138 | var isRemoved bool 139 | for i, configFile := range c.configFiles { 140 | isRemoved = removeVariableN(&configFile.content, parseSectionString(section), variable, n) 141 | if isRemoved { 142 | c.configFiles[i].changed = true 143 | return nil 144 | } 145 | } 146 | return errors.New("Variable not found (Not removed).") 147 | } 148 | -------------------------------------------------------------------------------- /hyprlang_parser_test.go: -------------------------------------------------------------------------------- 1 | package hyprlang_parser_test 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/anotherhadi/hyprlang-parser" 9 | ) 10 | 11 | const configPath = "test_config/example.conf" 12 | 13 | func strShouldEqual(t *testing.T, str1 string, str2 string) { 14 | if str1 != str2 { 15 | t.Error("Strings '" + str1 + "' and '" + str2 + "' not equal") 16 | } 17 | } 18 | 19 | func strsShouldEqual(t *testing.T, strs1 []string, strs2 []string) { 20 | if !reflect.DeepEqual(strs1, strs2) { 21 | t.Error("[]Strings '" + fmt.Sprint(strs1) + "' and '" + fmt.Sprint(strs2) + "' not equal") 22 | } 23 | } 24 | 25 | func failOnError(t *testing.T, err error) { 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | } 30 | 31 | func TestLoadConfig(t *testing.T) { 32 | _, err := hyprlang_parser.LoadConfig(configPath) 33 | if err != nil { 34 | t.Error(err) 35 | } 36 | } 37 | 38 | func TestWriteConfig(t *testing.T) { 39 | config, err := hyprlang_parser.LoadConfig(configPath) 40 | if err != nil { 41 | t.Error(err) 42 | } 43 | err = config.WriteConfig() 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | } 48 | 49 | func TestGetFirst(t *testing.T) { 50 | config, _ := hyprlang_parser.LoadConfig(configPath) 51 | 52 | strShouldEqual(t, config.GetFirst("", "exec-once"), "hyprlang_parser") 53 | strShouldEqual(t, config.GetFirst("", "exec_once"), "") 54 | strShouldEqual(t, config.GetFirst("input", "kb_layout"), "fr") 55 | strShouldEqual(t, config.GetFirst("input/", "kb_layout"), "fr") 56 | strShouldEqual(t, config.GetFirst("/input/", "kb_layout"), "fr") 57 | strShouldEqual(t, config.GetFirst("", "kb_layout"), "") 58 | strShouldEqual(t, config.GetFirst("input/touchpad/", "natural_scroll"), "yes") 59 | strShouldEqual(t, config.GetFirst("/input/touchpad", "natural_scroll"), "yes") 60 | 61 | // Sourced files 62 | strShouldEqual(t, config.GetFirst("", "only-sourced-var"), "ok") 63 | strShouldEqual(t, config.GetFirst("section/subsection", "does-it-work"), "true") 64 | } 65 | 66 | func TestGetAll(t *testing.T) { 67 | config, _ := hyprlang_parser.LoadConfig(configPath) 68 | 69 | strsShouldEqual(t, config.GetAll("", "monitor"), []string{"eDP-1,2240x1400@60,0x0,1", "eDP-2,2240x1400@60,0x0,1"}) 70 | } 71 | 72 | func TestEditFirst(t *testing.T) { 73 | config, _ := hyprlang_parser.LoadConfig(configPath) 74 | 75 | strShouldEqual(t, config.GetFirst("input", "kb_layout"), "fr") 76 | failOnError(t, config.EditFirst("input/", "kb_layout", "en")) 77 | strShouldEqual(t, config.GetFirst("input", "kb_layout"), "en") 78 | 79 | strShouldEqual(t, config.GetFirst("input/touchpad", "natural_scroll"), "yes") 80 | failOnError(t, config.EditFirst("input/touchpad", "natural_scroll", "no")) 81 | strShouldEqual(t, config.GetFirst("input/touchpad", "natural_scroll"), "no") 82 | 83 | strShouldEqual(t, config.GetFirst("", "monitor"), "eDP-1,2240x1400@60,0x0,1") 84 | failOnError(t, config.EditFirst("", "monitor", "no monitor here")) 85 | strShouldEqual(t, config.GetFirst("", "monitor"), "no monitor here") 86 | 87 | strShouldEqual(t, config.GetFirst("", "only-sourced-var"), "ok") 88 | failOnError(t, config.EditFirst("", "only-sourced-var", "youpi")) 89 | strShouldEqual(t, config.GetFirst("", "only-sourced-var"), "youpi") 90 | } 91 | 92 | func TestEditN(t *testing.T) { 93 | config, _ := hyprlang_parser.LoadConfig(configPath) 94 | 95 | strShouldEqual(t, config.GetAll("", "exec-once")[1], "hyprlang_parser_2") 96 | failOnError(t, config.EditN("/", "exec-once", "hyprsettings", 1)) 97 | strShouldEqual(t, config.GetAll("", "exec-once")[1], "hyprsettings") 98 | } 99 | 100 | func TestAdd(t *testing.T) { 101 | config, _ := hyprlang_parser.LoadConfig(configPath) 102 | 103 | strShouldEqual(t, config.GetFirst("", "a-new-var"), "") 104 | config.Add("", "a-new-var", "newvalue") 105 | strShouldEqual(t, config.GetFirst("", "a-new-var"), "newvalue") 106 | 107 | strShouldEqual(t, config.GetFirst("test", "a-new-var"), "") 108 | config.Add("test", "a-new-var", "newvalue") 109 | strShouldEqual(t, config.GetFirst("test", "a-new-var"), "newvalue") 110 | 111 | strShouldEqual(t, config.GetFirst("test/subsections", "a-new-var"), "") 112 | config.Add("test/subsections", "a-new-var", "newvalue") 113 | strShouldEqual(t, config.GetFirst("test/subsections", "a-new-var"), "newvalue") 114 | } 115 | 116 | func TestRemoveFirst(t *testing.T) { 117 | config, _ := hyprlang_parser.LoadConfig(configPath) 118 | 119 | strShouldEqual(t, config.GetFirst("", "monitor"), "eDP-1,2240x1400@60,0x0,1") 120 | failOnError(t, config.RemoveFirst("", "monitor")) 121 | strShouldEqual(t, config.GetFirst("", "monitor"), "eDP-2,2240x1400@60,0x0,1") 122 | 123 | failOnError(t, config.RemoveFirst("animations", "enabled")) 124 | strShouldEqual(t, config.GetFirst("animations", "enabled"), "") 125 | } 126 | 127 | func TestRemoveN(t *testing.T) { 128 | config, _ := hyprlang_parser.LoadConfig(configPath) 129 | 130 | strShouldEqual(t, config.GetAll("animations", "animation")[1], "border,1,10,default") 131 | failOnError(t, config.RemoveN("animations", "animation", 1)) 132 | strShouldEqual(t, config.GetAll("animations", "animation")[1], "fade,0,5,default") 133 | 134 | } 135 | -------------------------------------------------------------------------------- /line.go: -------------------------------------------------------------------------------- 1 | package hyprlang_parser 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func formatLine(line string) string { 8 | line = strings.TrimSpace(line) 9 | if !strings.Contains(line, "#") { 10 | return line 11 | } 12 | for i, letter := range line { 13 | if letter == '#' { 14 | if i-1 >= 0 && line[i-1] == '#' { 15 | continue 16 | } 17 | if i+1 < len(line)-1 && line[i+1] == '#' { 18 | continue 19 | } 20 | return line[:i] 21 | } 22 | } 23 | return line 24 | } 25 | 26 | func skipLine(line string) bool { 27 | line = formatLine(line) 28 | if line == "" { 29 | return true 30 | } 31 | if strings.HasPrefix(line, "#") { 32 | return true 33 | } 34 | 35 | return false 36 | } 37 | 38 | type lineType int 39 | 40 | const ( 41 | lineSectionStart lineType = 0 42 | lineSectionEnd lineType = 1 43 | lineVariable lineType = 2 44 | ) 45 | 46 | func getLineType(line string) lineType { 47 | line = formatLine(line) 48 | if strings.HasSuffix(line, "{") { 49 | return lineSectionStart 50 | } 51 | if strings.HasPrefix(line, "}") { 52 | return lineSectionEnd 53 | } 54 | return lineVariable 55 | } 56 | 57 | func parseLineVariable(line string) (name, value string) { 58 | line = formatLine(line) 59 | lineSplitted := strings.SplitN(line, "=", 2) 60 | name = strings.TrimSpace(lineSplitted[0]) 61 | value = strings.TrimSpace(lineSplitted[1]) 62 | return 63 | } 64 | 65 | func parseLineSectionStart(line string) (sectionName string) { 66 | line = formatLine(line) 67 | sectionName = strings.TrimSuffix(line, "{") 68 | sectionName = strings.TrimSpace(sectionName) 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | package hyprlang_parser 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | // Return only one value if n == -1 9 | func getVariables(content, section []string, variable string, n int) []string { 10 | var values []string 11 | var currentSection []string = []string{} 12 | for _, line := range content { 13 | if skipLine(line) { 14 | continue 15 | } 16 | currentLineType := getLineType(line) 17 | if currentLineType == lineSectionStart { 18 | currentSection = append(currentSection, parseLineSectionStart(line)) 19 | } else if currentLineType == lineSectionEnd { 20 | if len(currentSection) > 0 { 21 | currentSection = currentSection[:len(currentSection)-1] 22 | } 23 | } else if currentLineType == lineVariable { 24 | if !reflect.DeepEqual(currentSection, section) { 25 | continue 26 | } 27 | name, value := parseLineVariable(line) 28 | if name == variable { 29 | values = append(values, value) 30 | if n == -1 { 31 | return values 32 | } 33 | } 34 | } 35 | } 36 | return values 37 | } 38 | 39 | func editVariableN(content *[]string, section []string, variable string, newValue string, n int, indentation int) bool { 40 | var currentSection []string = []string{} 41 | var i int 42 | for lineNumber, line := range *content { 43 | if skipLine(line) { 44 | continue 45 | } 46 | currentLineType := getLineType(line) 47 | if currentLineType == lineSectionStart { 48 | currentSection = append(currentSection, parseLineSectionStart(line)) 49 | } else if currentLineType == lineSectionEnd { 50 | if len(currentSection) > 0 { 51 | currentSection = currentSection[:len(currentSection)-1] 52 | } 53 | } else if currentLineType == lineVariable { 54 | if !reflect.DeepEqual(currentSection, section) { 55 | continue 56 | } 57 | name, _ := parseLineVariable(line) 58 | if name == variable { 59 | if i == n { 60 | (*content)[lineNumber] = strings.Repeat(" ", indentation*len(currentSection)) + name + "=" + newValue 61 | return true 62 | } 63 | i++ 64 | } 65 | } 66 | } 67 | return false 68 | } 69 | 70 | func removeVariableN(content *[]string, section []string, variable string, n int) bool { 71 | var currentSection []string = []string{} 72 | var i int 73 | for lineNumber, line := range *content { 74 | if skipLine(line) { 75 | continue 76 | } 77 | currentLineType := getLineType(line) 78 | if currentLineType == lineSectionStart { 79 | currentSection = append(currentSection, parseLineSectionStart(line)) 80 | } else if currentLineType == lineSectionEnd { 81 | if len(currentSection) > 0 { 82 | currentSection = currentSection[:len(currentSection)-1] 83 | } 84 | } else if currentLineType == lineVariable { 85 | if !reflect.DeepEqual(currentSection, section) { 86 | continue 87 | } 88 | name, _ := parseLineVariable(line) 89 | if name == variable { 90 | if i == n { 91 | remove(content, lineNumber) 92 | return true 93 | } 94 | i++ 95 | } 96 | } 97 | } 98 | return false 99 | } 100 | 101 | func doesSectionExist(content *[]string, section []string) bool { 102 | if len(section) == 0 { 103 | return true 104 | } 105 | var currentSection []string = []string{} 106 | for _, line := range *content { 107 | if skipLine(line) { 108 | continue 109 | } 110 | currentLineType := getLineType(line) 111 | if currentLineType == lineSectionStart { 112 | currentSection = append(currentSection, parseLineSectionStart(line)) 113 | if reflect.DeepEqual(currentSection, section) { 114 | return true 115 | } 116 | } else if currentLineType == lineSectionEnd { 117 | if len(currentSection) > 0 { 118 | currentSection = currentSection[:len(currentSection)-1] 119 | } 120 | } 121 | } 122 | return false 123 | } 124 | 125 | func getIndentation(content *[]string) int { 126 | for lineNumber, line := range *content { 127 | if skipLine(line) { 128 | continue 129 | } 130 | currentLineType := getLineType(line) 131 | if currentLineType == lineSectionStart { 132 | if lineNumber < len(*content) { 133 | count := 0 134 | for _, char := range (*content)[lineNumber+1] { 135 | if char == ' ' { 136 | count++ 137 | } else { 138 | break 139 | } 140 | } 141 | return count 142 | } 143 | } 144 | } 145 | return 2 146 | } 147 | 148 | func addSection(content *[]string, section []string, newSection string, indentation int) { 149 | var currentSection []string = []string{} 150 | for lineNumber, line := range *content { 151 | if skipLine(line) { 152 | continue 153 | } 154 | if reflect.DeepEqual(currentSection, section) { 155 | insert(content, lineNumber, strings.Repeat(" ", indentation*len(currentSection))+newSection+" {") 156 | insert(content, lineNumber+1, strings.Repeat(" ", indentation*len(currentSection))+"}") 157 | return 158 | } 159 | currentLineType := getLineType(line) 160 | if currentLineType == lineSectionStart { 161 | currentSection = append(currentSection, parseLineSectionStart(line)) 162 | } else if currentLineType == lineSectionEnd { 163 | if len(currentSection) > 0 { 164 | currentSection = currentSection[:len(currentSection)-1] 165 | } 166 | } 167 | } 168 | } 169 | 170 | func addVariable(content *[]string, section []string, variable string, value string, indentation int) { 171 | var currentSection []string = []string{} 172 | for lineNumber, line := range *content { 173 | if skipLine(line) { 174 | continue 175 | } 176 | if reflect.DeepEqual(currentSection, section) { 177 | insert(content, lineNumber, strings.Repeat(" ", (len(currentSection)+1)*indentation)+variable+"="+value) 178 | return 179 | } 180 | currentLineType := getLineType(line) 181 | if currentLineType == lineSectionStart { 182 | currentSection = append(currentSection, parseLineSectionStart(line)) 183 | } else if currentLineType == lineSectionEnd { 184 | if len(currentSection) > 0 { 185 | currentSection = currentSection[:len(currentSection)-1] 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /test_config/example.conf: -------------------------------------------------------------------------------- 1 | # Not my actual conf 2 | 3 | source = test_config/test.conf 4 | 5 | ########## on startup ########## 6 | 7 | exec-once=hyprlang_parser 8 | exec-once = hyprlang_parser_2 9 | exec-once=gsettings set org.gnome.desktop.wm.preferences theme Kripton 10 | 11 | exec-once=swaybg -i $HOME/.config/background 12 | exec-once=dunst 13 | exec-once=bash $HOME/.config/eww/scripts/launch_bar 14 | exec-once=bash $HOME/.config/eww/scripts/launch_dashboard 15 | exec-once = swayidle -w timeout 300 'swaylock' before-sleep 'swaylock -f' 16 | 17 | 18 | 19 | ######### system config ########## 20 | 21 | input { 22 | kb_layout= fr 23 | kb_variant= 24 | kb_model= 25 | kb_options= 26 | kb_rules= 27 | 28 | follow_mouse=1 29 | numlock_by_default=1 30 | 31 | touchpad { 32 | natural_scroll=yes 33 | } 34 | } 35 | 36 | gestures { 37 | workspace_swipe = true 38 | #workspace_swipe_fingers = 3 39 | #workspace_swipe_distance = 300 40 | workspace_swipe_invert = true 41 | workspace_swipe_min_speed_to_force = 10 42 | workspace_swipe_cancel_ratio = 0.85 43 | } 44 | 45 | 46 | general { 47 | sensitivity=1.2 48 | 49 | gaps_in=2 50 | gaps_out=3 51 | border_size=3 52 | col.active_border=0xff7c94bf 53 | col.inactive_border=0x00ffffff 54 | } 55 | 56 | dwindle { 57 | pseudotile=0 # enable pseudotiling on dwindle 58 | force_split=2 # always on the right/bottom 59 | } 60 | 61 | misc { 62 | no_vfr=1 63 | } 64 | 65 | ########## window decorations and styling ########## 66 | 67 | decoration { 68 | rounding=6 69 | multisample_edges=1 # enable antialiasing for rounded corners 70 | 71 | active_opacity=1 72 | inactive_opacity=1 73 | } 74 | 75 | animations { 76 | enabled=1 77 | 78 | animation=windows,1,2,default 79 | animation=border,1,10,default 80 | animation=fade,0,5,default 81 | animation=workspaces,1,4,default 82 | } 83 | 84 | ########## monitor layout ########## 85 | 86 | monitor=eDP-1,2240x1400@60,0x0,1 87 | monitor=eDP-2,2240x1400@60,0x0,1 88 | 89 | ########## window rules ########## 90 | -------------------------------------------------------------------------------- /test_config/test.conf: -------------------------------------------------------------------------------- 1 | only-sourced-var=ok 2 | 3 | section { 4 | subsection { 5 | does-it-work=true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package hyprlang_parser 2 | 3 | import "strings" 4 | 5 | func insert(slice *[]string, index int, value string) { 6 | if len(*slice) == index { 7 | *slice = append(*slice, value) 8 | return 9 | } 10 | *slice = append((*slice)[:index+1], (*slice)[index:]...) 11 | (*slice)[index] = value 12 | } 13 | 14 | func remove(slice *[]string, index int) { 15 | *slice = append((*slice)[:index], (*slice)[index+1:]...) 16 | } 17 | 18 | func parseSectionString(section string) []string { 19 | // / -> [] 20 | // /general -> [general] 21 | // /general/ -> [general] 22 | // -> [] 23 | // /general/input -> [general, input] 24 | section = strings.TrimPrefix(section, "/") 25 | section = strings.TrimSuffix(section, "/") 26 | if section == "" { 27 | return []string{} 28 | } 29 | return strings.Split(section, "/") 30 | } 31 | --------------------------------------------------------------------------------