diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b746a90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Binaries for programs and plugins +bin/* +multini +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Custom binary test +tests/out_ini/* + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5150321 --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +NAME=multini +VERSION=0.1 +GOCMD=go +LDFLAGS:="$(LDFLAGS) -X 'main.Version=$(VERSION)'" +GOBUILD=$(GOCMD) build -ldflags=$(LDFLAGS) +SRCMAIN=. +BINDIR=bin +BIN=$(BINDIR)/$(NAME) +README=README +LICENSE=LICENSE +TEST=./run_test.sh +PREFIX=/usr + +all: build + +prep: clean + mkdir $(BINDIR) + +build: prep + $(GOBUILD) -o $(BIN) $(SRCMAIN) + +check-build: + test -e $(BIN) + +freebsd-386: prep + GOOS=freebsd GOARCH=386 $(GOBUILD) -o $(BIN)-freebsd-386 $(SRCMAIN) + +darwin-386: prep + GOOS=darwin GOARCH=386 $(GOBUILD) -o $(BIN)-darwin-386 $(SRCMAIN) + +linux-386: prep + GOOS=linux GOARCH=386 $(GOBUILD) -o $(BIN)-linux-386 $(SRCMAIN) + +windows-386: prep + GOOS=windows GOARCH=386 $(GOBUILD) -o $(BIN)-windows-386.exe $(SRCMAIN) + +freebsd-amd64: prep + GOOS=freebsd GOARCH=amd64 $(GOBUILD) -o $(BIN)-freebsd-amd64 $(SRCMAIN) + +darwin-amd64: prep + GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BIN)-darwin-amd64 $(SRCMAIN) + +linux-amd64: prep + GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BIN)-linux-amd64 $(SRCMAIN) + +windows-amd64: prep + GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BIN)-windows-amd64.exe $(SRCMAIN) + +compile: freebsd-386 darwin-386 linux-386 windows-386 freebsd-amd64 darwin-amd64 linux-amd64 windows-amd64 + +install: check-build + install -m 755 -d $(PREFIX)/bin/ + install -m 755 $(BIN) $(PREFIX)/bin/ + install -m 755 -d $(PREFIX)/share/licenses/$(NAME)/ + install -m 644 $(LICENSE) $(PREFIX)/share/licenses/$(NAME)/ + install -m 755 -d $(PREFIX)/share/doc/$(NAME)/ + install -m 644 $(README) $(PREFIX)/share/doc/$(NAME)/ + +check-install: + test -e $(PREFIX)/bin/$(NAME) || \ + test -d $(PREFIX)/share/licenses/$(NAME) || \ + test -d $(PREFIX)/share/doc/$(NAME) + +uninstall: check-install + rm -f $(PREFIX)/bin/$(NAME) + rm -rf $(PREFIX)/share/licenses/$(NAME) + rm -rf $(PREFIX)/share/doc/$(NAME) + +clean: + rm -rf $(BINDIR) + +test: check-build + $(TEST) $(BIN) diff --git a/README b/README new file mode 100644 index 0000000..7f46841 --- /dev/null +++ b/README @@ -0,0 +1,22 @@ +A utility for manipulating ini files with duplicate keys + +Usage: multini [OPTION]... [ACTION] config_file [section] [param] [value] +Actions: + -g, --get get values for a given combination of parameters. + -s, --set set values for a given combination of parameters. + -a, --add add values for a given combination of parameters. + -d, --del delete the given combination of parameters. + -c, --chk display parsing errors for the specified file. + +Options: + -e, --existing For --set and --del, fail if item is missing. + -i, --inplace Lock and write files in place. + This is not atomic but has less restrictions + than the default replacement method. + -o, --output FILE Write output to FILE instead. '-' means stdout + -u, --unix Use LF as end of line + -w, --windows Use CRLF as end of line + -h, --help Write this help to stdout + --version Write version to stdout + +2020 (c) GenZmeY diff --git a/actions.go b/actions.go new file mode 100644 index 0000000..63775c1 --- /dev/null +++ b/actions.go @@ -0,0 +1,68 @@ +package main + +import ( + "multini/output" + "multini/types" +) + +func chk() int { + var ok bool + var err error + ok, err = iniCheck(ArgFile) + if err != nil { + output.Errorln(err) + return EXIT_FILE_READ_ERR + } + if ok { + return EXIT_SUCCESS + } else { + return EXIT_BAD_SYNTAX_ERR + } +} + +func add(ini *types.Ini) error { + if ArgKeyIsSet { + return ini.AddKey(ArgSection, ArgKey, ArgValue) + } else { + ini.AddSection(ArgSection) + return nil + } +} + +func set(ini *types.Ini) error { + if ArgKeyIsSet { + return ini.SetKey(ArgSection, ArgKey, ArgValue) + } else { + ini.SetSection(ArgSection) + return nil + } +} + +func get(ini *types.Ini) error { + var err error = nil + var res string + if ArgValueIsSet { + return ini.GetKeyVal(ArgSection, ArgKey, ArgValue) + } else if ArgKeyIsSet { + res, err = ini.GetKey(ArgSection, ArgKey) + } else if ArgSectionIsSet { + res, err = ini.GetSection(ArgSection) + } else { + res = ini.Get() + } + + if err == nil { + output.Println(res) + } + return err +} + +func del(ini *types.Ini) error { + if ArgValueIsSet { + return ini.DelKeyVal(ArgSection, ArgKey, ArgValue) + } else if ArgKeyIsSet { + return ini.DelKey(ArgSection, ArgKey) + } else { + return ini.DelSection(ArgSection) + } +} diff --git a/args.go b/args.go new file mode 100644 index 0000000..2b07e33 --- /dev/null +++ b/args.go @@ -0,0 +1,177 @@ +package main + +import ( + "errors" + "os" + + "multini/output" + "multini/types" + + "github.com/juju/gnuflag" +) + +var ( + ArgInplace bool + ArgGet bool + ArgSet bool + ArgAdd bool + ArgDel bool + ArgChk bool + ArgVerbose bool + ArgVersion bool + ArgWindows bool + ArgUnix bool + ArgHelp bool + ArgExisting bool + ArgOutput string + + ArgFile string + ArgFileIsSet bool = false + ArgSection string + ArgSectionIsSet bool = false + ArgKey string + ArgKeyIsSet bool = false + ArgValue string + ArgValueIsSet bool = false +) + +func printHelp() { + output.Println("A utility for manipulating ini files with duplicate keys") + output.Println("") + output.Println("Usage: multini [OPTION]... [ACTION] config_file [section] [param] [value]") + output.Println("Actions:") + output.Println(" -g, --get get values for a given combination of parameters.") + output.Println(" -s, --set set values for a given combination of parameters.") + output.Println(" -a, --add add values for a given combination of parameters.") + output.Println(" -d, --del delete the given combination of parameters.") + output.Println(" -c, --chk display parsing errors for the specified file.") + output.Println("") + output.Println("Options:") + output.Println(" -e, --existing For --set and --del, fail if item is missing.") + output.Println(" -i, --inplace Lock and write files in place.") + output.Println(" This is not atomic but has less restrictions") + output.Println(" than the default replacement method.") + output.Println(" -o, --output FILE Write output to FILE instead. '-' means stdout") + // output.Println(" -v, --verbose Indicate on stderr if changes were made") + output.Println(" -u, --unix Use LF as end of line") + output.Println(" -w, --windows Use CRLF as end of line") + output.Println(" -h, --help Write this help to stdout") + output.Println(" --version Write version to stdout") +} + +func printVersion() { + output.Println("multini ", Version) +} + +func init() { + gnuflag.BoolVar(&ArgGet, "get", false, "") + gnuflag.BoolVar(&ArgGet, "g", false, "") + gnuflag.BoolVar(&ArgAdd, "add", false, "") + gnuflag.BoolVar(&ArgAdd, "a", false, "") + gnuflag.BoolVar(&ArgSet, "set", false, "") + gnuflag.BoolVar(&ArgSet, "s", false, "") + gnuflag.BoolVar(&ArgDel, "del", false, "") + gnuflag.BoolVar(&ArgDel, "d", false, "") + gnuflag.BoolVar(&ArgChk, "chk", false, "") + gnuflag.BoolVar(&ArgChk, "c", false, "") + gnuflag.BoolVar(&ArgDel, "inplace", false, "") + gnuflag.BoolVar(&ArgDel, "i", false, "") + gnuflag.BoolVar(&ArgUnix, "unix", false, "") + gnuflag.BoolVar(&ArgUnix, "u", false, "") + gnuflag.BoolVar(&ArgWindows, "windows", false, "") + gnuflag.BoolVar(&ArgWindows, "w", false, "") + gnuflag.BoolVar(&ArgExisting, "existing", false, "") + gnuflag.BoolVar(&ArgExisting, "e", false, "") + gnuflag.BoolVar(&ArgVerbose, "verbose", false, "") + gnuflag.BoolVar(&ArgVerbose, "v", false, "") + gnuflag.StringVar(&ArgOutput, "output", "", "") + gnuflag.StringVar(&ArgOutput, "o", "", "") + gnuflag.BoolVar(&ArgVersion, "version", false, "") + gnuflag.BoolVar(&ArgHelp, "help", false, "") + gnuflag.BoolVar(&ArgHelp, "h", false, "") +} + +func parseArgs() error { + gnuflag.Parse(true) + + // info + switch { + case ArgHelp: + printHelp() + os.Exit(EXIT_SUCCESS) + case ArgVersion: + printVersion() + os.Exit(EXIT_SUCCESS) + } + + // File EOF + switch { + case ArgWindows: + types.SetEndOfLineWindows() + case ArgUnix: + types.SetEndOfLineUnix() + default: + types.SetEndOfLineNative() + } + + // Output settings + output.SetEndOfLineNative() + output.SetVerbose(ArgVerbose) + + // Positional Args + for i := 0; i < 4 && i < gnuflag.NArg(); i++ { + switch i { + case 0: + ArgFile = gnuflag.Arg(0) + ArgFileIsSet = true + case 1: + ArgSection = gnuflag.Arg(1) + ArgSectionIsSet = true + case 2: + ArgKey = gnuflag.Arg(2) + ArgKeyIsSet = true + case 3: + ArgValue = gnuflag.Arg(3) + ArgValueIsSet = true + } + } + + if !ArgFileIsSet { + return errors.New("Config_file not specified") + } + + // Mode + actionCounter := 0 + if ArgChk { + actionCounter++ + } + if ArgDel { + actionCounter++ + if !ArgSectionIsSet { + return errors.New("Section not specified") + } + } + if ArgGet { + actionCounter++ + } + if ArgSet { + actionCounter++ + if !ArgSectionIsSet { + return errors.New("Section not specified") + } + } + if ArgAdd { + actionCounter++ + if !ArgSectionIsSet { + return errors.New("Section not specified") + } + } + switch actionCounter { + case 0: + return errors.New("Action not set") + case 1: + return nil + default: + return errors.New("Only one action can be used at the same time") + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..40e3390 --- /dev/null +++ b/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "os" + + "multini/output" + "multini/types" +) + +const ( + EXIT_SUCCESS int = 0 + EXIT_ARG_ERR int = 1 + EXIT_FILE_READ_ERR int = 2 + EXIT_BAD_SYNTAX_ERR int = 3 + EXIT_ELEMENT_NOT_FOUND int = 4 + EXIT_FILE_WRITE_ERR int = 5 + EXIT_ACTION_ERR int = 6 +) + +var ( + Version string = "development" +) + +func main() { + var err error + var ini types.Ini + + if err = parseArgs(); err != nil { + output.Errorln(err) + os.Exit(EXIT_ARG_ERR) + } + + if ArgChk { + os.Exit(chk()) + } + + ini, err = iniRead(ArgFile) + if err != nil { + fmt.Println(err) + os.Exit(EXIT_FILE_READ_ERR) + } + + switch { + case ArgGet: + err = get(&ini) + case ArgAdd: + err = add(&ini) + case ArgDel: + err = del(&ini) + case ArgSet: + err = set(&ini) + } + + if err != nil { + output.Errorln(err) + os.Exit(EXIT_ACTION_ERR) + } + + if ArgOutput == "-" { + output.Println(ini.Full()) + os.Exit(EXIT_SUCCESS) + } else if ArgOutput != "" { + ArgFile = ArgOutput + } + + if ArgAdd || ArgSet || ArgDel { + if ArgInplace || ArgOutput != "" { + err = iniWriteInplace(ArgFile, &ini) + } else { + err = iniWrite(ArgFile, &ini) + } + if err != nil { + output.Errorln(err) + os.Exit(EXIT_FILE_WRITE_ERR) + } + } + + os.Exit(EXIT_SUCCESS) +} diff --git a/output/output.go b/output/output.go new file mode 100644 index 0000000..b61f4a2 --- /dev/null +++ b/output/output.go @@ -0,0 +1,82 @@ +package output + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "runtime" +) + +var ( + endOfLine string = "\n" + devNull *log.Logger = log.New(ioutil.Discard, "", 0) + stdout *log.Logger = log.New(os.Stdout, "", 0) + stderr *log.Logger = log.New(os.Stderr, "", 0) + verbose *log.Logger = devNull +) + +func SetVerbose(enabled bool) { + if enabled { + verbose = stderr + } else { + verbose = devNull + } +} + +func SetEndOfLineNative() { + switch os := runtime.GOOS; os { + case "windows": + setEndOfLineWindows() + default: + setEndOfLineUnix() + } +} + +func EOL() string { + return endOfLine +} + +func setEndOfLineUnix() { + endOfLine = "\n" +} + +func setEndOfLineWindows() { + endOfLine = "\r\n" +} + +func Print(v ...interface{}) { + stdout.Print(v...) +} + +func Printf(format string, v ...interface{}) { + stdout.Printf(format, v...) +} + +func Println(v ...interface{}) { + stdout.Print(fmt.Sprint(v...) + endOfLine) +} + +func Error(v ...interface{}) { + stderr.Print(v...) +} + +func Errorf(format string, v ...interface{}) { + stderr.Printf(format, v...) +} + +func Errorln(v ...interface{}) { + stderr.Print(fmt.Sprint(v...) + endOfLine) +} + +func Verbose(v ...interface{}) { + verbose.Print(v...) +} + +func Verbosef(format string, v ...interface{}) { + verbose.Printf(format, v...) +} + +func Verboseln(v ...interface{}) { + verbose.Print(fmt.Sprint(v...) + endOfLine) +} diff --git a/reader.go b/reader.go new file mode 100644 index 0000000..7325b04 --- /dev/null +++ b/reader.go @@ -0,0 +1,182 @@ +package main + +import ( + "bufio" + "errors" + "os" + "regexp" + "strings" + + "multini/output" + "multini/types" +) + +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` + + 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) +) + +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() { + if i != 0 && name != "" && i <= len(match) { + result[name] = match[i] + } + } + return result +} + +func debugMap(el map[string]string) string { + var dbgMap strings.Builder + for key, val := range el { + dbgMap.WriteString(" " + key + ": \"" + val + "\"" + output.EOL()) + } + return dbgMap.String() +} + +func appendLine(ini *types.Ini, line string) error { + elements := rxParse(RxCompiled, 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) + return nil + } else { + output.Verboseln("Got:", newSection.Line()) + var newTrash types.Trash = types.Trash{Value: line} + ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newTrash) + } + case elements[NgKey] != "": + var newKeyValue types.KeyValue + newKeyValue.Key = elements[NgKey] + newKeyValue.PostfixKey = elements[NgKeyPostfix] + newKeyValue.PrefixKey = elements[NgPrefix] + newKeyValue.Value = elements[NgValue] + 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 + } else { + output.Verboseln("Got:", newKeyValue.Line()) + var newTrash types.Trash = types.Trash{Value: line} + ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newTrash) + } + case elements[NgComment] != "": + var newComment types.Comment + newComment.Prefix = elements[NgPrefix] + elements[NgCommentPrefix] + newComment.Value = elements[NgComment] + 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 + } else { + output.Verboseln("Got:", newComment.Line()) + var newTrash types.Trash = types.Trash{Value: line} + ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newTrash) + } + case elements[NgPrefix] != "" || line == "": + var newEmptyLine types.EmptyLine + newEmptyLine.Prefix = elements[NgPrefix] + if newEmptyLine.Line() == line { + ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newEmptyLine) + return nil + } else { + output.Verboseln("Got:", newEmptyLine.Line()) + var newTrash types.Trash = types.Trash{Value: line} + ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newTrash) + } + default: + var newTrash types.Trash = types.Trash{Value: line} + ini.Sections[len(ini.Sections)-1].(*types.Section).Lines = append(ini.Sections[len(ini.Sections)-1].(*types.Section).Lines, &newTrash) + } + return errors.New(debugMap(elements)) +} + +func iniRead(filename string) (types.Ini, error) { + var ( + err error + iniFile *os.File + ini types.Ini + ) + iniFile, err = os.Open(filename) + + if err != nil { + return ini, err + } + + fileScanner := bufio.NewScanner(iniFile) + fileScanner.Split(bufio.ScanLines) + ini.Init() + + for i := 1; fileScanner.Scan(); i++ { + appendLine(&ini, fileScanner.Text()) + } + + iniFile.Close() + return ini, nil +} + +func iniCheck(filename string) (bool, error) { + var ( + err error + iniFile *os.File + ini types.Ini + ok bool = true + ) + iniFile, err = os.Open(filename) + + if err != nil { + return ok, err + } + + fileScanner := bufio.NewScanner(iniFile) + fileScanner.Split(bufio.ScanLines) + ini.Init() + + for i := 1; fileScanner.Scan(); i++ { + line := fileScanner.Text() + err = appendLine(&ini, line) + if err != nil { + output.Errorln(i, ":", line) + output.Verboseln(err) + ok = false + } + } + + iniFile.Close() + return ok, nil +} diff --git a/run_test.sh b/run_test.sh new file mode 100755 index 0000000..096030c --- /dev/null +++ b/run_test.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +DEF='\e[0m'; BLD='\e[1m'; RED='\e[31m'; GRN='\e[32m'; WHT='\e[97m' +ScriptFullname=$(readlink -e "$0") +ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}') +ScriptDir=$(dirname "$ScriptFullname") +TestDir="$ScriptDir/tests" +Multini=$(readlink -e "$1") + +if [[ -z "$Multini" ]]; then + Multini="$ScriptDir/multini" +fi + +RetCode=0 +pushd "$TestDir" > /dev/null +while read Test +do + echo -ne "${BLD}${WHT}[----]${DEF} $Test" + Errors=$("$TestDir/$Test" "$Multini") + if [[ $? -ne 0 ]]; then + echo -e "\r${BLD}${WHT}[${RED}FAIL${WHT}]${DEF} $Test" + RetCode=1 + echo "$Errors" + else + echo -e "\r${BLD}${WHT}[${GRN} OK ${WHT}]${DEF} $Test" + fi +done < <(find "$TestDir" -type f -name 'test_*.sh' -printf "%f\n") +popd > /dev/null +exit "$RetCode" diff --git a/tests/common.sh b/tests/common.sh new file mode 100755 index 0000000..51d8d77 --- /dev/null +++ b/tests/common.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +ScriptFullname=$(readlink -e "$0") +ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}') +ScriptDir=$(dirname "$ScriptFullname") +Multini="$1" +InDir="$ScriptDir/in_ini" +InIni="$InDir/$ScriptName.ini" +OutDir="$ScriptDir/out_ini" +OutIni="$OutDir/$ScriptName.ini" +ExpectedIni="$ScriptDir/expected_ini/$ScriptName.ini" + +if [[ ! -d "$OutDir" ]]; then + mkdir -p "$OutDir" +fi + +rm -f "$OutIni" + +function compare () +{ + if ! cmp -s "$OutIni" "$ExpectedIni" ; then + diff "$OutIni" "$ExpectedIni" + return 1 + fi + rm -f "$OutIni" + return 0 +} diff --git a/tests/expected_ini/test_add_keyval.sh.ini b/tests/expected_ini/test_add_keyval.sh.ini new file mode 100644 index 0000000..0394419 --- /dev/null +++ b/tests/expected_ini/test_add_keyval.sh.ini @@ -0,0 +1,20 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + Key3 = 3 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/expected_ini/test_add_keyval_existing.sh.ini b/tests/expected_ini/test_add_keyval_existing.sh.ini new file mode 100644 index 0000000..1097509 --- /dev/null +++ b/tests/expected_ini/test_add_keyval_existing.sh.ini @@ -0,0 +1,20 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + Key = 4 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/expected_ini/test_add_section.sh.ini b/tests/expected_ini/test_add_section.sh.ini new file mode 100644 index 0000000..a528b1a --- /dev/null +++ b/tests/expected_ini/test_add_section.sh.ini @@ -0,0 +1,20 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] +[NewSection] diff --git a/tests/expected_ini/test_del_key.sh.ini b/tests/expected_ini/test_del_key.sh.ini new file mode 100644 index 0000000..893fe31 --- /dev/null +++ b/tests/expected_ini/test_del_key.sh.ini @@ -0,0 +1,18 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/expected_ini/test_del_key_multiple.sh.ini b/tests/expected_ini/test_del_key_multiple.sh.ini new file mode 100644 index 0000000..49b2155 --- /dev/null +++ b/tests/expected_ini/test_del_key_multiple.sh.ini @@ -0,0 +1,16 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/expected_ini/test_del_keyval.sh.ini b/tests/expected_ini/test_del_keyval.sh.ini new file mode 100644 index 0000000..6d56286 --- /dev/null +++ b/tests/expected_ini/test_del_keyval.sh.ini @@ -0,0 +1,18 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/expected_ini/test_del_section.sh.ini b/tests/expected_ini/test_del_section.sh.ini new file mode 100644 index 0000000..6e8709a --- /dev/null +++ b/tests/expected_ini/test_del_section.sh.ini @@ -0,0 +1,17 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + +[SectionWithoutNewLineBefore] diff --git a/tests/expected_ini/test_get_key.sh.ini b/tests/expected_ini/test_get_key.sh.ini new file mode 100644 index 0000000..20bb86d --- /dev/null +++ b/tests/expected_ini/test_get_key.sh.ini @@ -0,0 +1 @@ +Some Value2 And Tabs! diff --git a/tests/expected_ini/test_get_key_multiple.sh.ini b/tests/expected_ini/test_get_key_multiple.sh.ini new file mode 100644 index 0000000..01e79c3 --- /dev/null +++ b/tests/expected_ini/test_get_key_multiple.sh.ini @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/tests/expected_ini/test_get_keyval.sh.ini b/tests/expected_ini/test_get_keyval.sh.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/expected_ini/test_get_section.sh.ini b/tests/expected_ini/test_get_section.sh.ini new file mode 100644 index 0000000..58aabb7 --- /dev/null +++ b/tests/expected_ini/test_get_section.sh.ini @@ -0,0 +1,7 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + diff --git a/tests/expected_ini/test_get_section_list.sh.ini b/tests/expected_ini/test_get_section_list.sh.ini new file mode 100644 index 0000000..eed0f4c --- /dev/null +++ b/tests/expected_ini/test_get_section_list.sh.ini @@ -0,0 +1,5 @@ + +SimpleSection +MultipleKeySection +SectionWithIndent +SectionWithoutNewLineBefore diff --git a/tests/expected_ini/test_set_keyval.sh.ini b/tests/expected_ini/test_set_keyval.sh.ini new file mode 100644 index 0000000..dac4738 --- /dev/null +++ b/tests/expected_ini/test_set_keyval.sh.ini @@ -0,0 +1,17 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = onlyone + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_add_keyval.sh.ini b/tests/in_ini/test_add_keyval.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_add_keyval.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_add_keyval_existing.sh.ini b/tests/in_ini/test_add_keyval_existing.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_add_keyval_existing.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_add_section.sh.ini b/tests/in_ini/test_add_section.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_add_section.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_del_key.sh.ini b/tests/in_ini/test_del_key.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_del_key.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_del_key_multiple.sh.ini b/tests/in_ini/test_del_key_multiple.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_del_key_multiple.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_del_keyval.sh.ini b/tests/in_ini/test_del_keyval.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_del_keyval.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_del_section.sh.ini b/tests/in_ini/test_del_section.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_del_section.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_get_key.sh.ini b/tests/in_ini/test_get_key.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_get_key.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_get_key_multiple.sh.ini b/tests/in_ini/test_get_key_multiple.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_get_key_multiple.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_get_keyval.sh.ini b/tests/in_ini/test_get_keyval.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_get_keyval.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_get_section.sh.ini b/tests/in_ini/test_get_section.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_get_section.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_get_section_list.sh.ini b/tests/in_ini/test_get_section_list.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_get_section_list.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/in_ini/test_set_keyval.sh.ini b/tests/in_ini/test_set_keyval.sh.ini new file mode 100644 index 0000000..e96cbb7 --- /dev/null +++ b/tests/in_ini/test_set_keyval.sh.ini @@ -0,0 +1,19 @@ +# comment +; comment again + +DefKey1 = Some Value1 +DefKey2 = Some Value2 And Tabs! # With Comment +DefKey3=NoSpaces! + +[SimpleSection] # Comment For Section + Key1 = 1 + Key2 = 2 + +[MultipleKeySection] + Key = 1 + Key = 2 + Key = 3 + + [SectionWithIndent] + Key=Value +[SectionWithoutNewLineBefore] diff --git a/tests/test_add_keyval.sh b/tests/test_add_keyval.sh new file mode 100755 index 0000000..96debc7 --- /dev/null +++ b/tests/test_add_keyval.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --add "$InIni" SimpleSection Key3 3 + +compare diff --git a/tests/test_add_keyval_existing.sh b/tests/test_add_keyval_existing.sh new file mode 100755 index 0000000..7049c04 --- /dev/null +++ b/tests/test_add_keyval_existing.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --add "$InIni" MultipleKeySection Key 4 + +compare diff --git a/tests/test_add_section.sh b/tests/test_add_section.sh new file mode 100755 index 0000000..59df2da --- /dev/null +++ b/tests/test_add_section.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --add "$InIni" NewSection + +compare diff --git a/tests/test_del_key.sh b/tests/test_del_key.sh new file mode 100755 index 0000000..9570332 --- /dev/null +++ b/tests/test_del_key.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --del "$InIni" SimpleSection Key1 + +compare diff --git a/tests/test_del_key_multiple.sh b/tests/test_del_key_multiple.sh new file mode 100755 index 0000000..12d405e --- /dev/null +++ b/tests/test_del_key_multiple.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --del "$InIni" MultipleKeySection Key + +compare diff --git a/tests/test_del_keyval.sh b/tests/test_del_keyval.sh new file mode 100755 index 0000000..7126cfa --- /dev/null +++ b/tests/test_del_keyval.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --del "$InIni" MultipleKeySection Key 2 + +compare diff --git a/tests/test_del_section.sh b/tests/test_del_section.sh new file mode 100755 index 0000000..b641a0d --- /dev/null +++ b/tests/test_del_section.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --del "$InIni" SectionWithIndent + +compare diff --git a/tests/test_get_key.sh b/tests/test_get_key.sh new file mode 100755 index 0000000..2d7caa0 --- /dev/null +++ b/tests/test_get_key.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini --get "$InIni" '' DefKey2 > "$OutIni" + +compare diff --git a/tests/test_get_key_multiple.sh b/tests/test_get_key_multiple.sh new file mode 100755 index 0000000..1d5dda0 --- /dev/null +++ b/tests/test_get_key_multiple.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini --get "$InIni" MultipleKeySection Key > "$OutIni" + +compare diff --git a/tests/test_get_keyval.sh b/tests/test_get_keyval.sh new file mode 100755 index 0000000..cdba71a --- /dev/null +++ b/tests/test_get_keyval.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini --get "$InIni" MultipleKeySection Key 3 > "$OutIni" + +compare diff --git a/tests/test_get_section.sh b/tests/test_get_section.sh new file mode 100755 index 0000000..cb93f93 --- /dev/null +++ b/tests/test_get_section.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini --get "$InIni" '' > "$OutIni" + +compare diff --git a/tests/test_get_section_list.sh b/tests/test_get_section_list.sh new file mode 100755 index 0000000..133445b --- /dev/null +++ b/tests/test_get_section_list.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini --get "$InIni" > "$OutIni" + +compare diff --git a/tests/test_set_keyval.sh b/tests/test_set_keyval.sh new file mode 100755 index 0000000..03b7a1d --- /dev/null +++ b/tests/test_set_keyval.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +source "common.sh" + +$Multini -o "$OutIni" --set "$InIni" MultipleKeySection Key onlyone + +compare diff --git a/types/comment.go b/types/comment.go new file mode 100644 index 0000000..5f8919f --- /dev/null +++ b/types/comment.go @@ -0,0 +1,26 @@ +package types + +type Comment struct { + Prefix string + Value string +} + +func (obj *Comment) Full() string { + return obj.Prefix + obj.Value +} + +func (obj *Comment) Line() string { + return obj.Prefix + obj.Value +} + +func (obj *Comment) Fulln() string { + return obj.Full() + endOfLine +} + +func (obj *Comment) Type() TElement { + return TComment +} + +func (obj *Comment) Indent() string { + return obj.Prefix +} diff --git a/types/common.go b/types/common.go new file mode 100644 index 0000000..b7ea8ea --- /dev/null +++ b/types/common.go @@ -0,0 +1,50 @@ +package types + +import ( + "runtime" +) + +type TElement int + +const ( + TDeleted TElement = 0 + TComment TElement = 1 + TEmptyLine TElement = 2 + TKeyValue TElement = 3 + TSection TElement = 4 + TTrash TElement = 5 +) + +var ( + endOfLine string = "\n" + existing bool = false +) + +func SetEndOfLineNative() { + switch os := runtime.GOOS; os { + case "windows": + SetEndOfLineWindows() + default: + SetEndOfLineUnix() + } +} + +func SetEndOfLineUnix() { + endOfLine = "\n" +} + +func SetEndOfLineWindows() { + endOfLine = "\r\n" +} + +func SetExistingMode(value bool) { + existing = value +} + +func createIfNotExist() bool { + return !existing +} + +func failIfNotExist() bool { + return existing +} diff --git a/types/deleted.go b/types/deleted.go new file mode 100644 index 0000000..02327f0 --- /dev/null +++ b/types/deleted.go @@ -0,0 +1,24 @@ +package types + +type Deleted struct { +} + +func (obj *Deleted) Full() string { + return "" +} + +func (obj *Deleted) Line() string { + return "" +} + +func (obj *Deleted) Fulln() string { + return "" +} + +func (obj *Deleted) Type() TElement { + return TDeleted +} + +func (obj *Deleted) Indent() string { + return "" +} diff --git a/types/element.go b/types/element.go new file mode 100644 index 0000000..bf12d9c --- /dev/null +++ b/types/element.go @@ -0,0 +1,9 @@ +package types + +type Element interface { + Full() string + Fulln() string + Line() string + Indent() string + Type() TElement +} diff --git a/types/emptyline.go b/types/emptyline.go new file mode 100644 index 0000000..9cfb7ef --- /dev/null +++ b/types/emptyline.go @@ -0,0 +1,25 @@ +package types + +type EmptyLine struct { + Prefix string +} + +func (obj *EmptyLine) Full() string { + return obj.Prefix +} + +func (obj *EmptyLine) Line() string { + return obj.Prefix +} + +func (obj *EmptyLine) Fulln() string { + return obj.Full() + endOfLine +} + +func (obj *EmptyLine) Type() TElement { + return TEmptyLine +} + +func (obj *EmptyLine) Indent() string { + return obj.Prefix +} diff --git a/types/ini.go b/types/ini.go new file mode 100644 index 0000000..518810b --- /dev/null +++ b/types/ini.go @@ -0,0 +1,152 @@ +package types + +import ( + "errors" + "strings" +) + +type Ini struct { + Sections []Element +} + +func (obj *Ini) Init() { + obj.Sections = append(obj.Sections, &Section{}) // global section +} + +func (obj *Ini) Full() string { + var body strings.Builder + for _, line := range obj.Sections { + body.WriteString(line.Fulln()) + } + return body.String() +} + +func (obj *Ini) FindSection(name string) (*Section, error) { + for i, sect := range obj.Sections { + if sect.(*Section).Name == name { + return obj.Sections[i].(*Section), nil + } + } + return nil, errors.New("Section not found: " + name) +} + +func (obj *Ini) DelSection(name string) error { + gotIt := false + for i, sect := range obj.Sections { + if sect.Type() == TSection && + sect.(*Section).Name == name { + obj.Sections[i] = &Deleted{} + gotIt = true + } + } + if gotIt { + return nil + } else if failIfNotExist() { + return errors.New("Section not found: " + name) + } else { + return nil + } +} + +func (obj *Ini) Get() string { + var sectList strings.Builder + for _, sect := range obj.Sections { + name := sect.(*Section).Name + if name != "" || len(sect.(*Section).Lines) > 0 { + sectList.WriteString(name + endOfLine) + } + } + return strings.TrimSuffix(sectList.String(), endOfLine) +} + +func (obj *Ini) GetSection(section string) (string, error) { + sect, err := obj.FindSection(section) + if err != nil { + return "", err + } else { + return sect.Full(), nil + } +} + +func (obj *Ini) GetKey(section, key string) (string, error) { + sect, err := obj.FindSection(section) + if err != nil { + return "", err + } else { + return sect.GetKey(key) + } +} + +func (obj *Ini) GetKeyVal(section, key, value string) error { + sect, err := obj.FindSection(section) + if err != nil { + return err + } else { + return sect.GetKeyVal(key, value) + } +} + +func (obj *Ini) AddSection(section string) *Section { + sect, err := obj.FindSection(section) + if err != nil { + var newSection Section + newSection.Name = section + newSection.Prefix = obj.Sections[len(obj.Sections)-1].Indent() + sect = &newSection + obj.Sections = append(obj.Sections, sect) + } + return sect +} + +func (obj *Ini) SetSection(section string) *Section { + return obj.AddSection(section) +} + +func (obj *Ini) AddKey(section, key, value string) error { + sect, err := obj.FindSection(section) + if err != nil { + if createIfNotExist() { + sect = obj.AddSection(section) + } else { + return err + } + } + sect.AddKey(key, value) + return nil +} + +func (obj *Ini) SetKey(section, key, value string) error { + sect, err := obj.FindSection(section) + if err != nil { + if createIfNotExist() { + sect = obj.AddSection(section) + } else { + return err + } + } + return sect.SetKey(key, value) +} + +func (obj *Ini) DelKey(section, key string) error { + sect, err := obj.FindSection(section) + if err != nil { + if failIfNotExist() { + return err + } else { + return nil + } + } + return sect.DelKey(key) +} + +func (obj *Ini) DelKeyVal(section, key, value string) error { + sect, err := obj.FindSection(section) + if err != nil { + if failIfNotExist() { + return err + } else { + return nil + } + } + return sect.DelKeyVal(key, value) +} diff --git a/types/keyvalue.go b/types/keyvalue.go new file mode 100644 index 0000000..5339fd0 --- /dev/null +++ b/types/keyvalue.go @@ -0,0 +1,38 @@ +package types + +type KeyValue struct { + PrefixKey string + Key string + PostfixKey string + PrefixValue string + Value string + PostfixValue string + Comment Comment +} + +func (obj *KeyValue) Full() string { + return obj.PrefixKey + + obj.Key + + obj.PostfixKey + + "=" + + obj.PrefixValue + + obj.Value + + obj.PostfixValue + + obj.Comment.Full() +} + +func (obj *KeyValue) Line() string { + return obj.Full() +} + +func (obj *KeyValue) Fulln() string { + return obj.Full() + endOfLine +} + +func (obj *KeyValue) Type() TElement { + return TKeyValue +} + +func (obj *KeyValue) Indent() string { + return obj.PrefixKey +} diff --git a/types/section.go b/types/section.go new file mode 100644 index 0000000..b67c337 --- /dev/null +++ b/types/section.go @@ -0,0 +1,185 @@ +package types + +import ( + "errors" + "strings" +) + +type Section struct { + Prefix string + Name string + Postfix string + Comment Comment + Lines []Element +} + +func (obj *Section) Headern() string { + if obj.Name == "" { + return "" + } else { + return obj.Prefix + "[" + obj.Name + "]" + obj.Postfix + obj.Comment.Full() + endOfLine + } +} + +func (obj *Section) Line() string { + return obj.Header() +} + +func (obj *Section) Header() string { + return strings.TrimSuffix(obj.Headern(), endOfLine) +} + +func (obj *Section) Fulln() string { + var body strings.Builder + for _, line := range obj.Lines { + body.WriteString(line.Fulln()) + } + return obj.Headern() + body.String() +} + +func (obj *Section) Full() string { + return strings.TrimSuffix(obj.Fulln(), endOfLine) +} + +func (obj *Section) Type() TElement { + return TSection +} + +func (obj *Section) Indent() string { + return obj.Prefix +} + +func (obj *Section) DelKey(name string) error { + gotIt := false + for i, keyVal := range obj.Lines { + if keyVal.Type() == TKeyValue && + keyVal.(*KeyValue).Key == name { + obj.Lines[i] = &Deleted{} + gotIt = true + } + } + if gotIt { + return nil + } else if failIfNotExist() { + return errors.New("Parameter not found: " + name) + } else { + return nil + } +} + +func (obj *Section) DelKeyVal(name, value string) error { + gotIt := false + for i, keyVal := range obj.Lines { + if keyVal.Type() == TKeyValue && + keyVal.(*KeyValue).Key == name && + keyVal.(*KeyValue).Value == value { + obj.Lines[i] = &Deleted{} + gotIt = true + } + } + if gotIt { + return nil + } else if failIfNotExist() { + return errors.New("Parameter:value pair not found: " + name + ":" + value) + } else { + return nil + } +} + +func (obj *Section) GetKey(name string) (string, error) { + var err error = nil + var result strings.Builder + for _, keyVal := range obj.Lines { + if keyVal.Type() == TKeyValue && keyVal.(*KeyValue).Key == name { + result.WriteString(keyVal.(*KeyValue).Value + endOfLine) + } + } + if result.String() == "" { + err = errors.New("Parameter not found: " + name) + } + return strings.TrimSuffix(result.String(), endOfLine), err +} + +func (obj *Section) GetKeyVal(name, value string) error { + for _, keyVal := range obj.Lines { + if keyVal.Type() == TKeyValue && + keyVal.(*KeyValue).Key == name && + keyVal.(*KeyValue).Value == value { + return nil + } + } + return errors.New("Parameter:Value not found: " + name + ":" + value) +} + +func (obj *Section) appendKey(name, value string) { + var newKeyValue KeyValue + var replaceIndex int = -1 + newKeyValue.Key = name + newKeyValue.Value = value + // replace first emptyline + for i := len(obj.Lines) - 1; i >= 0; i-- { + if obj.Lines[i].Type() == TEmptyLine { + replaceIndex = i + } else { + break + } + } + // for right indent and tabs + for i := len(obj.Lines) - 1; i >= 0; i-- { + if obj.Lines[i].Type() == TKeyValue { + template := obj.Lines[i].(*KeyValue) + newKeyValue.PrefixKey = template.PrefixKey + newKeyValue.PostfixKey = template.PostfixKey + newKeyValue.PrefixValue = template.PrefixValue + newKeyValue.PostfixValue = template.PostfixValue + break + } + } + if replaceIndex == -1 { + obj.Lines = append(obj.Lines, &newKeyValue) + } else { + obj.Lines = append(obj.Lines, obj.Lines[replaceIndex]) + obj.Lines[replaceIndex] = &newKeyValue + } +} + +func (obj *Section) AddKey(name, value string) { + gotIt := false + for i, keyVal := range obj.Lines { + if keyVal.Type() == TKeyValue && + keyVal.(*KeyValue).Key == name && + keyVal.(*KeyValue).Value == value { + if !gotIt { + gotIt = true + } else { + obj.Lines[i] = &Deleted{} + } + } + } + if !gotIt { + obj.appendKey(name, value) + } +} + +func (obj *Section) SetKey(name, value string) error { + gotIt := false + for i, keyVal := range obj.Lines { + if keyVal.Type() == TKeyValue && + keyVal.(*KeyValue).Key == name { + if !gotIt { + keyVal.(*KeyValue).Value = value + gotIt = true + } else { + obj.Lines[i] = &Deleted{} + } + } + } + if !gotIt { + if createIfNotExist() { + obj.appendKey(name, value) + } else { + return errors.New("Parameter not found: " + name) + } + } + return nil +} diff --git a/types/trash.go b/types/trash.go new file mode 100644 index 0000000..c818fb1 --- /dev/null +++ b/types/trash.go @@ -0,0 +1,25 @@ +package types + +type Trash struct { + Value string +} + +func (obj *Trash) Full() string { + return obj.Value +} + +func (obj *Trash) Line() string { + return obj.Value +} + +func (obj *Trash) Fulln() string { + return obj.Full() + endOfLine +} + +func (obj *Trash) Type() TElement { + return TTrash +} + +func (obj *Trash) Indent() string { + return "" +} diff --git a/writer.go b/writer.go new file mode 100644 index 0000000..e214bde --- /dev/null +++ b/writer.go @@ -0,0 +1,46 @@ +package main + +import ( + "bufio" + "io/ioutil" + "os" + + "multini/types" +) + +func replaceOriginal(oldFile, newFile string) error { + err := os.Remove(oldFile) + if err == nil { + err = os.Rename(newFile, oldFile) + } + return err +} + +func iniWrite(filename string, ini *types.Ini) error { + tmpFile, err := ioutil.TempFile(os.TempDir(), "multini") + if err == nil { + datawriter := bufio.NewWriter(tmpFile) + _, err = datawriter.WriteString(ini.Full()) + if err == nil { + err = datawriter.Flush() + tmpFile.Close() + if err == nil { + err = replaceOriginal(filename, tmpFile.Name()) + } + } + } + return err +} + +func iniWriteInplace(filename string, ini *types.Ini) error { + targetFile, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644) + if err == nil { + datawriter := bufio.NewWriter(targetFile) + _, err = datawriter.WriteString(ini.Full()) + if err == nil { + err = datawriter.Flush() + targetFile.Close() + } + } + return err +}