refactor: improved line parser

- improved support for C-style comments;
- add support for keys with square brackets;
- slightly improved speed.
This commit is contained in:
GenZmeY 2020-11-09 17:03:57 +03:00
parent a5976526ae
commit c258e6096f
8 changed files with 118 additions and 106 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/bin/ /bin/
/test/data/out_ini/ /test/data/out_ini/
/cmd/multini/multini

View File

@ -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 <genzmey@gmail.com> - 0.3.0-1
- multini-0.3.0:
- add C-style comments support.
* Thu Apr 30 2020 GenZmeY <genzmey@gmail.com> - 0.2.3-1
- multini-0.2.3:
- fixed file write for the '--inplace' option.
* Wed Apr 29 2020 GenZmeY <genzmey@gmail.com> - 0.2.2-1
- multini-0.2.2.
* Wed Apr 29 2020 GenZmeY <genzmey@gmail.com> - 0.2.1-1
- multini-0.2.1:
- fix "rename invalid cross-device link".
* Mon Apr 27 2020 GenZmeY <genzmey@gmail.com> - 0.2-1
- multini-0.2:
- fix inplace arg;
- follow symlinks by default;
- reverse flag.
* Sun Apr 26 2020 GenZmeY <genzmey@gmail.com> - 0.1-1
- First version of spec.

View File

@ -22,36 +22,90 @@ var (
NgValuePrefix string = `value_prefix` NgValuePrefix string = `value_prefix`
NgValuePostfix string = `value_postfix` NgValuePostfix string = `value_postfix`
NgComment string = `comment` NgComment string = `comment`
NgCommentPrefix string = `comment_prefix` NgData string = `data`
RxBodyPrefix string = `(?P<` + NgPrefix + `>\s+)?` RxEmpty string = `^(?P<` + NgPrefix + `>\s+)?$`
RxSectionName string = `\[(?P<` + NgSection + `>.+)\]` RxSection string = `^(?P<` + NgPrefix + `>\s+)?\[(?P<` + NgSection + `>[^\]]+)\](?P<` + NgPostifx + `>\s+)?$`
RxKey string = `(?P<` + NgKey + `>(?:[^;#/=]+[^\s=;#/]|[^;#/=]))?` RxKey string = `^(?P<` + NgPrefix + `>\s+)?(?P<` + NgKey + `>.*[^\s]+)(?P<` + NgKeyPostfix + `>\s+)?$`
RxKeyPostfix string = `(?P<` + NgKeyPostfix + `>\s+)?` RxValue string = `^(?P<` + NgValuePrefix + `>\s+)?(?P<` + NgValue + `>.*[^\s])(?P<` + NgValuePostfix + `>\s+)?$`
RxValuePrefix string = `(?P<` + NgValuePrefix + `>\s+)?`
RxValue string = `(?P<` + NgValue + `>(?:[^;#/]+[^\s;#/]|[^;#/]))?` RxEmptyCompile *regexp.Regexp = regexp.MustCompile(RxEmpty)
RxValuePostfix string = `(?P<` + NgValuePostfix + `>\s+)?` RxSectionCompile *regexp.Regexp = regexp.MustCompile(RxSection)
RxKeyVal string = RxKey + RxKeyPostfix + `=` + RxValuePrefix + RxValue + RxValuePostfix RxKeyCompile *regexp.Regexp = regexp.MustCompile(RxKey)
RxBody string = `(?:` + RxSectionName + `|` + RxKeyVal + `)?` RxValueCompile *regexp.Regexp = regexp.MustCompile(RxValue)
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)
) )
func rxParse(rx *regexp.Regexp, str string) map[string]string { func parse(str string) map[string]string {
match := rx.FindStringSubmatch(str) var result map[string]string = make(map[string]string)
result := make(map[string]string) var data string
for i, name := range rx.SubexpNames() {
if i != 0 && name != "" && i <= len(match) { data, result[NgComment] = getDataComment(str)
result[name] = match[i]
} if data != "" {
findNamedGroups(data, RxEmptyCompile, &result)
} }
if result[NgPrefix] != "" {
return result 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]
}
}
}
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 { func debugMap(el map[string]string) string {
var dbgMap strings.Builder var dbgMap strings.Builder
for key, val := range el { for key, val := range el {
@ -61,14 +115,14 @@ func debugMap(el map[string]string) string {
} }
func appendLine(ini *types.Ini, line string) error { func appendLine(ini *types.Ini, line string) error {
elements := rxParse(RxCompiled, line) // elements := rxParse(line)
elements := parse(line)
switch { switch {
case elements[NgSection] != "": case elements[NgSection] != "":
var newSection types.Section var newSection types.Section
newSection.Name = elements[NgSection] newSection.Name = elements[NgSection]
newSection.Prefix = elements[NgPrefix] newSection.Prefix = elements[NgPrefix]
newSection.Postfix = elements[NgPostifx] newSection.Postfix = elements[NgPostifx]
newSection.Comment.Prefix = elements[NgCommentPrefix]
newSection.Comment.Value = elements[NgComment] newSection.Comment.Value = elements[NgComment]
if newSection.Line() == line { if newSection.Line() == line {
ini.Sections = append(ini.Sections, &newSection) ini.Sections = append(ini.Sections, &newSection)
@ -87,7 +141,6 @@ func appendLine(ini *types.Ini, line string) error {
newKeyValue.PrefixValue = elements[NgValuePrefix] newKeyValue.PrefixValue = elements[NgValuePrefix]
newKeyValue.PostfixValue = elements[NgValuePostfix] newKeyValue.PostfixValue = elements[NgValuePostfix]
newKeyValue.Comment.Value = elements[NgComment] newKeyValue.Comment.Value = elements[NgComment]
newKeyValue.Comment.Prefix = elements[NgCommentPrefix]
if newKeyValue.Line() == line { if newKeyValue.Line() == line {
ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newKeyValue) ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newKeyValue)
return nil return nil
@ -98,8 +151,8 @@ func appendLine(ini *types.Ini, line string) error {
} }
case elements[NgComment] != "": case elements[NgComment] != "":
var newComment types.Comment var newComment types.Comment
newComment.Prefix = elements[NgPrefix] + elements[NgCommentPrefix]
newComment.Value = elements[NgComment] newComment.Value = elements[NgComment]
newComment.Prefix = elements[NgPrefix]
if newComment.Line() == line { if newComment.Line() == line {
ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newComment) ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newComment)
return nil return nil

View File

@ -1,6 +1,6 @@
# comment # comment
; comment again ; comment again
// comment with indent
DefKey1 = Some Value1 DefKey1 = Some Value1
DefKey2 = Some Value2 And Tabs! # With Comment DefKey2 = Some Value2 And Tabs! # With Comment
DefKey3=NoSpaces! DefKey3=NoSpaces!

View File

@ -0,0 +1 @@
skip/skip

View File

@ -1,6 +1,6 @@
# comment # comment
; comment again ; comment again
// comment with indent
DefKey1 = Some Value1 DefKey1 = Some Value1
DefKey2 = Some Value2 And Tabs! # With Comment DefKey2 = Some Value2 And Tabs! # With Comment
DefKey3=NoSpaces! DefKey3=NoSpaces!

View File

@ -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]

View File

@ -0,0 +1,7 @@
#!/bin/bash
source "common.sh"
$Multini --get "$InIni" 'Slashes/Test' './Dir1/File' > "$OutIni"
compare