0.1
This commit is contained in:
parent
753cb7bfd6
commit
294a89ba74
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@ -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/
|
73
Makefile
Normal file
73
Makefile
Normal file
@ -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)
|
22
README
Normal file
22
README
Normal file
@ -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
|
68
actions.go
Normal file
68
actions.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
177
args.go
Normal file
177
args.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
80
main.go
Normal file
80
main.go
Normal file
@ -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)
|
||||||
|
}
|
82
output/output.go
Normal file
82
output/output.go
Normal file
@ -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)
|
||||||
|
}
|
182
reader.go
Normal file
182
reader.go
Normal file
@ -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
|
||||||
|
}
|
29
run_test.sh
Executable file
29
run_test.sh
Executable file
@ -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"
|
27
tests/common.sh
Executable file
27
tests/common.sh
Executable file
@ -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
|
||||||
|
}
|
20
tests/expected_ini/test_add_keyval.sh.ini
Normal file
20
tests/expected_ini/test_add_keyval.sh.ini
Normal file
@ -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]
|
20
tests/expected_ini/test_add_keyval_existing.sh.ini
Normal file
20
tests/expected_ini/test_add_keyval_existing.sh.ini
Normal file
@ -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]
|
20
tests/expected_ini/test_add_section.sh.ini
Normal file
20
tests/expected_ini/test_add_section.sh.ini
Normal file
@ -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]
|
18
tests/expected_ini/test_del_key.sh.ini
Normal file
18
tests/expected_ini/test_del_key.sh.ini
Normal file
@ -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]
|
16
tests/expected_ini/test_del_key_multiple.sh.ini
Normal file
16
tests/expected_ini/test_del_key_multiple.sh.ini
Normal file
@ -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]
|
18
tests/expected_ini/test_del_keyval.sh.ini
Normal file
18
tests/expected_ini/test_del_keyval.sh.ini
Normal file
@ -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]
|
17
tests/expected_ini/test_del_section.sh.ini
Normal file
17
tests/expected_ini/test_del_section.sh.ini
Normal file
@ -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]
|
1
tests/expected_ini/test_get_key.sh.ini
Normal file
1
tests/expected_ini/test_get_key.sh.ini
Normal file
@ -0,0 +1 @@
|
|||||||
|
Some Value2 And Tabs!
|
3
tests/expected_ini/test_get_key_multiple.sh.ini
Normal file
3
tests/expected_ini/test_get_key_multiple.sh.ini
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
0
tests/expected_ini/test_get_keyval.sh.ini
Normal file
0
tests/expected_ini/test_get_keyval.sh.ini
Normal file
7
tests/expected_ini/test_get_section.sh.ini
Normal file
7
tests/expected_ini/test_get_section.sh.ini
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# comment
|
||||||
|
; comment again
|
||||||
|
|
||||||
|
DefKey1 = Some Value1
|
||||||
|
DefKey2 = Some Value2 And Tabs! # With Comment
|
||||||
|
DefKey3=NoSpaces!
|
||||||
|
|
5
tests/expected_ini/test_get_section_list.sh.ini
Normal file
5
tests/expected_ini/test_get_section_list.sh.ini
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
SimpleSection
|
||||||
|
MultipleKeySection
|
||||||
|
SectionWithIndent
|
||||||
|
SectionWithoutNewLineBefore
|
17
tests/expected_ini/test_set_keyval.sh.ini
Normal file
17
tests/expected_ini/test_set_keyval.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_add_keyval.sh.ini
Normal file
19
tests/in_ini/test_add_keyval.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_add_keyval_existing.sh.ini
Normal file
19
tests/in_ini/test_add_keyval_existing.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_add_section.sh.ini
Normal file
19
tests/in_ini/test_add_section.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_del_key.sh.ini
Normal file
19
tests/in_ini/test_del_key.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_del_key_multiple.sh.ini
Normal file
19
tests/in_ini/test_del_key_multiple.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_del_keyval.sh.ini
Normal file
19
tests/in_ini/test_del_keyval.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_del_section.sh.ini
Normal file
19
tests/in_ini/test_del_section.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_get_key.sh.ini
Normal file
19
tests/in_ini/test_get_key.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_get_key_multiple.sh.ini
Normal file
19
tests/in_ini/test_get_key_multiple.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_get_keyval.sh.ini
Normal file
19
tests/in_ini/test_get_keyval.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_get_section.sh.ini
Normal file
19
tests/in_ini/test_get_section.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_get_section_list.sh.ini
Normal file
19
tests/in_ini/test_get_section_list.sh.ini
Normal file
@ -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]
|
19
tests/in_ini/test_set_keyval.sh.ini
Normal file
19
tests/in_ini/test_set_keyval.sh.ini
Normal file
@ -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]
|
7
tests/test_add_keyval.sh
Executable file
7
tests/test_add_keyval.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --add "$InIni" SimpleSection Key3 3
|
||||||
|
|
||||||
|
compare
|
7
tests/test_add_keyval_existing.sh
Executable file
7
tests/test_add_keyval_existing.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --add "$InIni" MultipleKeySection Key 4
|
||||||
|
|
||||||
|
compare
|
7
tests/test_add_section.sh
Executable file
7
tests/test_add_section.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --add "$InIni" NewSection
|
||||||
|
|
||||||
|
compare
|
7
tests/test_del_key.sh
Executable file
7
tests/test_del_key.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --del "$InIni" SimpleSection Key1
|
||||||
|
|
||||||
|
compare
|
7
tests/test_del_key_multiple.sh
Executable file
7
tests/test_del_key_multiple.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --del "$InIni" MultipleKeySection Key
|
||||||
|
|
||||||
|
compare
|
7
tests/test_del_keyval.sh
Executable file
7
tests/test_del_keyval.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --del "$InIni" MultipleKeySection Key 2
|
||||||
|
|
||||||
|
compare
|
7
tests/test_del_section.sh
Executable file
7
tests/test_del_section.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --del "$InIni" SectionWithIndent
|
||||||
|
|
||||||
|
compare
|
7
tests/test_get_key.sh
Executable file
7
tests/test_get_key.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini --get "$InIni" '' DefKey2 > "$OutIni"
|
||||||
|
|
||||||
|
compare
|
7
tests/test_get_key_multiple.sh
Executable file
7
tests/test_get_key_multiple.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini --get "$InIni" MultipleKeySection Key > "$OutIni"
|
||||||
|
|
||||||
|
compare
|
7
tests/test_get_keyval.sh
Executable file
7
tests/test_get_keyval.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini --get "$InIni" MultipleKeySection Key 3 > "$OutIni"
|
||||||
|
|
||||||
|
compare
|
7
tests/test_get_section.sh
Executable file
7
tests/test_get_section.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini --get "$InIni" '' > "$OutIni"
|
||||||
|
|
||||||
|
compare
|
7
tests/test_get_section_list.sh
Executable file
7
tests/test_get_section_list.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini --get "$InIni" > "$OutIni"
|
||||||
|
|
||||||
|
compare
|
7
tests/test_set_keyval.sh
Executable file
7
tests/test_set_keyval.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "common.sh"
|
||||||
|
|
||||||
|
$Multini -o "$OutIni" --set "$InIni" MultipleKeySection Key onlyone
|
||||||
|
|
||||||
|
compare
|
26
types/comment.go
Normal file
26
types/comment.go
Normal file
@ -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
|
||||||
|
}
|
50
types/common.go
Normal file
50
types/common.go
Normal file
@ -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
|
||||||
|
}
|
24
types/deleted.go
Normal file
24
types/deleted.go
Normal file
@ -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 ""
|
||||||
|
}
|
9
types/element.go
Normal file
9
types/element.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
type Element interface {
|
||||||
|
Full() string
|
||||||
|
Fulln() string
|
||||||
|
Line() string
|
||||||
|
Indent() string
|
||||||
|
Type() TElement
|
||||||
|
}
|
25
types/emptyline.go
Normal file
25
types/emptyline.go
Normal file
@ -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
|
||||||
|
}
|
152
types/ini.go
Normal file
152
types/ini.go
Normal file
@ -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)
|
||||||
|
}
|
38
types/keyvalue.go
Normal file
38
types/keyvalue.go
Normal file
@ -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
|
||||||
|
}
|
185
types/section.go
Normal file
185
types/section.go
Normal file
@ -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
|
||||||
|
}
|
25
types/trash.go
Normal file
25
types/trash.go
Normal file
@ -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 ""
|
||||||
|
}
|
46
writer.go
Normal file
46
writer.go
Normal file
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user