From c258e6096fdb03c4a1652d51443f78b13a7a7090 Mon Sep 17 00:00:00 2001 From: GenZmeY Date: Mon, 9 Nov 2020 17:03:57 +0300 Subject: [PATCH] refactor: improved line parser - improved support for C-style comments; - add support for keys with square brackets; - slightly improved speed. --- .gitignore | 1 + build/srpm/multini.spec | 69 ---------- cmd/multini/reader.go | 123 +++++++++++++----- test/data/expected_ini/test_add_keyval.sh.ini | 2 +- .../expected_ini/test_get_key_slashes.sh.ini | 1 + test/data/in_ini/test_add_keyval.sh.ini | 2 +- test/data/in_ini/test_get_key_slashes.sh.ini | 19 +++ test/data/test_get_key_slashes.sh | 7 + 8 files changed, 118 insertions(+), 106 deletions(-) delete mode 100644 build/srpm/multini.spec create mode 100644 test/data/expected_ini/test_get_key_slashes.sh.ini create mode 100644 test/data/in_ini/test_get_key_slashes.sh.ini create mode 100755 test/data/test_get_key_slashes.sh diff --git a/.gitignore b/.gitignore index 145bd8e..fcd1080 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /bin/ /test/data/out_ini/ +/cmd/multini/multini diff --git a/build/srpm/multini.spec b/build/srpm/multini.spec deleted file mode 100644 index a207f40..0000000 --- a/build/srpm/multini.spec +++ /dev/null @@ -1,69 +0,0 @@ -# WARNING: offline build not supported :( - -%undefine _missing_build_ids_terminate_build -%global debug_package %{nil} - -Name: multini -Version: 0.3.0 -Release: 1%{dist} -Summary: A utility for manipulating ini files with duplicate keys -License: MIT -Url: https://github.com/GenZmeY/multini - -Source0: %{name}-%{version}.tar.gz - -BuildRequires: golang-bin >= 1.13 - -Provides: %{name} - -%description -A utility for easily manipulating ini files from the command line and shell scripts. - -%prep -%setup -q -c - -%build -make -j $(nproc) VERSION=%{version} - -%install -rm -rf $RPM_BUILD_ROOT -make install PREFIX=%{buildroot}/usr - -%check -make test - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%defattr(-,root,root) -%attr(755,root,root) %{_bindir}/%{name} -%attr(755,root,root) %dir %{_docdir}/%{name} -%attr(755,root,root) %dir %{_datadir}/licenses/%{name} -%attr(644,root,root) %{_docdir}/%{name}/README -%attr(644,root,root) %{_datadir}/licenses/%{name}/LICENSE - -%changelog -* Thu Apr 30 2020 GenZmeY - 0.3.0-1 -- multini-0.3.0: -- add C-style comments support. - -* Thu Apr 30 2020 GenZmeY - 0.2.3-1 -- multini-0.2.3: -- fixed file write for the '--inplace' option. - -* Wed Apr 29 2020 GenZmeY - 0.2.2-1 -- multini-0.2.2. - -* Wed Apr 29 2020 GenZmeY - 0.2.1-1 -- multini-0.2.1: -- fix "rename invalid cross-device link". - -* Mon Apr 27 2020 GenZmeY - 0.2-1 -- multini-0.2: -- fix inplace arg; -- follow symlinks by default; -- reverse flag. - -* Sun Apr 26 2020 GenZmeY - 0.1-1 -- First version of spec. diff --git a/cmd/multini/reader.go b/cmd/multini/reader.go index 34f7464..1a68917 100644 --- a/cmd/multini/reader.go +++ b/cmd/multini/reader.go @@ -13,43 +13,97 @@ import ( var ( // Ng - Named Group - NgPrefix string = `prefix` - NgPostifx string = `postfix` - NgSection string = `section` - NgKey string = `key` - NgKeyPostfix string = `key_postfix` - NgValue string = `value` - NgValuePrefix string = `value_prefix` - NgValuePostfix string = `value_postfix` - NgComment string = `comment` - NgCommentPrefix string = `comment_prefix` + NgPrefix string = `prefix` + NgPostifx string = `postfix` + NgSection string = `section` + NgKey string = `key` + NgKeyPostfix string = `key_postfix` + NgValue string = `value` + NgValuePrefix string = `value_prefix` + NgValuePostfix string = `value_postfix` + NgComment string = `comment` + NgData string = `data` - RxBodyPrefix string = `(?P<` + NgPrefix + `>\s+)?` - RxSectionName string = `\[(?P<` + NgSection + `>.+)\]` - RxKey string = `(?P<` + NgKey + `>(?:[^;#/=]+[^\s=;#/]|[^;#/=]))?` - RxKeyPostfix string = `(?P<` + NgKeyPostfix + `>\s+)?` - RxValuePrefix string = `(?P<` + NgValuePrefix + `>\s+)?` - RxValue string = `(?P<` + NgValue + `>(?:[^;#/]+[^\s;#/]|[^;#/]))?` - RxValuePostfix string = `(?P<` + NgValuePostfix + `>\s+)?` - RxKeyVal string = RxKey + RxKeyPostfix + `=` + RxValuePrefix + RxValue + RxValuePostfix - RxBody string = `(?:` + RxSectionName + `|` + RxKeyVal + `)?` - RxBodyPostfix string = `(?P<` + NgPostifx + `>\s+)?` - RxCommentPrefix string = `(?P<` + NgCommentPrefix + `>([#;]|//)\s*)` - RxCommentText string = `(?P<` + NgComment + `>.+)?` - RxComment string = `(?:` + RxCommentPrefix + RxCommentText + `)?` - Rx string = RxBodyPrefix + RxBody + RxBodyPostfix + RxComment - RxCompiled *regexp.Regexp = regexp.MustCompile(Rx) + RxEmpty string = `^(?P<` + NgPrefix + `>\s+)?$` + RxSection string = `^(?P<` + NgPrefix + `>\s+)?\[(?P<` + NgSection + `>[^\]]+)\](?P<` + NgPostifx + `>\s+)?$` + RxKey string = `^(?P<` + NgPrefix + `>\s+)?(?P<` + NgKey + `>.*[^\s]+)(?P<` + NgKeyPostfix + `>\s+)?$` + RxValue string = `^(?P<` + NgValuePrefix + `>\s+)?(?P<` + NgValue + `>.*[^\s])(?P<` + NgValuePostfix + `>\s+)?$` + + RxEmptyCompile *regexp.Regexp = regexp.MustCompile(RxEmpty) + RxSectionCompile *regexp.Regexp = regexp.MustCompile(RxSection) + RxKeyCompile *regexp.Regexp = regexp.MustCompile(RxKey) + RxValueCompile *regexp.Regexp = regexp.MustCompile(RxValue) ) -func rxParse(rx *regexp.Regexp, str string) map[string]string { - match := rx.FindStringSubmatch(str) - result := make(map[string]string) - for i, name := range rx.SubexpNames() { +func parse(str string) map[string]string { + var result map[string]string = make(map[string]string) + var data string + + data, result[NgComment] = getDataComment(str) + + if data != "" { + findNamedGroups(data, RxEmptyCompile, &result) + } + + if result[NgPrefix] != "" { + return result + } + + findNamedGroups(data, RxSectionCompile, &result) + + if result[NgSection] == "" && data != "" { + keyPart, valPart := getKeyValue(data) + findNamedGroups(keyPart, RxKeyCompile, &result) + findNamedGroups(valPart, RxValueCompile, &result) + } + + return result +} + +func findNamedGroups(str string, Rx *regexp.Regexp, result *map[string]string) { + match := Rx.FindStringSubmatch(str) + for i, name := range Rx.SubexpNames() { if i != 0 && name != "" && i <= len(match) { - result[name] = match[i] + (*result)[name] = match[i] } } - return result +} + +func getDataComment(str string) (string, string) { + var indexes []int + var commentIndex int = -1 + + indexes = append(indexes, strings.Index(str, "//")) + indexes = append(indexes, strings.Index(str, "#")) + indexes = append(indexes, strings.Index(str, ";")) + + for _, index := range indexes { + if commentIndex == -1 { + if index != -1 { + commentIndex = index + } + } else { + if index != -1 { + if commentIndex > index { + commentIndex = index + } + } + } + } + + if commentIndex == -1 { + return str, "" + } else { + return str[:commentIndex], str[commentIndex:] + } +} + +func getKeyValue(data string) (string, string) { + index := strings.Index(data, "=") + if index != -1 { + return data[:index], data[index+1:] + } + return "", "" } func debugMap(el map[string]string) string { @@ -61,14 +115,14 @@ func debugMap(el map[string]string) string { } func appendLine(ini *types.Ini, line string) error { - elements := rxParse(RxCompiled, line) + // elements := rxParse(line) + elements := parse(line) switch { case elements[NgSection] != "": var newSection types.Section newSection.Name = elements[NgSection] newSection.Prefix = elements[NgPrefix] newSection.Postfix = elements[NgPostifx] - newSection.Comment.Prefix = elements[NgCommentPrefix] newSection.Comment.Value = elements[NgComment] if newSection.Line() == line { ini.Sections = append(ini.Sections, &newSection) @@ -87,7 +141,6 @@ func appendLine(ini *types.Ini, line string) error { newKeyValue.PrefixValue = elements[NgValuePrefix] newKeyValue.PostfixValue = elements[NgValuePostfix] newKeyValue.Comment.Value = elements[NgComment] - newKeyValue.Comment.Prefix = elements[NgCommentPrefix] if newKeyValue.Line() == line { ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newKeyValue) return nil @@ -98,8 +151,8 @@ func appendLine(ini *types.Ini, line string) error { } case elements[NgComment] != "": var newComment types.Comment - newComment.Prefix = elements[NgPrefix] + elements[NgCommentPrefix] newComment.Value = elements[NgComment] + newComment.Prefix = elements[NgPrefix] if newComment.Line() == line { ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newComment) return nil diff --git a/test/data/expected_ini/test_add_keyval.sh.ini b/test/data/expected_ini/test_add_keyval.sh.ini index 3b7486d..87b21fe 100644 --- a/test/data/expected_ini/test_add_keyval.sh.ini +++ b/test/data/expected_ini/test_add_keyval.sh.ini @@ -1,6 +1,6 @@ # comment ; comment again - + // comment with indent DefKey1 = Some Value1 DefKey2 = Some Value2 And Tabs! # With Comment DefKey3=NoSpaces! diff --git a/test/data/expected_ini/test_get_key_slashes.sh.ini b/test/data/expected_ini/test_get_key_slashes.sh.ini new file mode 100644 index 0000000..2784217 --- /dev/null +++ b/test/data/expected_ini/test_get_key_slashes.sh.ini @@ -0,0 +1 @@ +skip/skip diff --git a/test/data/in_ini/test_add_keyval.sh.ini b/test/data/in_ini/test_add_keyval.sh.ini index 05bac2e..939aea5 100644 --- a/test/data/in_ini/test_add_keyval.sh.ini +++ b/test/data/in_ini/test_add_keyval.sh.ini @@ -1,6 +1,6 @@ # comment ; comment again - + // comment with indent DefKey1 = Some Value1 DefKey2 = Some Value2 And Tabs! # With Comment DefKey3=NoSpaces! diff --git a/test/data/in_ini/test_get_key_slashes.sh.ini b/test/data/in_ini/test_get_key_slashes.sh.ini new file mode 100644 index 0000000..650aab6 --- /dev/null +++ b/test/data/in_ini/test_get_key_slashes.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[Slashes/Test] // Comment For Section + ./Dir1/File = skip/skip // comment + Key2 = 2 + +[MultipleKeySection] // C style comment + Key = 1 +Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/test/data/test_get_key_slashes.sh b/test/data/test_get_key_slashes.sh new file mode 100755 index 0000000..b6d8a0b --- /dev/null +++ b/test/data/test_get_key_slashes.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini --get "$InIni" 'Slashes/Test' './Dir1/File' > "$OutIni" + +compare