11 Commits
0.1 ... 0.3.0

Author SHA1 Message Date
287547b69e add github actions workflow 2020-11-03 17:11:59 +03:00
f4934225b6 Ah shit, here we go again 2020-11-03 13:14:35 +03:00
69be8312f8 add C-style comment support 2020-11-03 11:20:24 +03:00
f083dcd3d8 enable go modules 2020-11-03 11:19:37 +03:00
44e955a047 change project structure 2020-11-03 10:47:19 +03:00
c76140ffe1 release: 0.2.3
- fixed file write for the '--inplace' option
2020-04-30 08:15:26 +03:00
a89e63f19f fix: file write for the '--inplace' option
removes tails for --inplace in case
the source file is larger than the modified one
2020-04-30 08:09:53 +03:00
0ea49ff1ad release: 0.2.2
- stop parsing options when pos args started
- add sections with newline before
2020-04-29 11:38:02 +03:00
325c6c25d7 release: 0.2.1
fix "rename invalid cross-device link"
2020-04-29 05:31:19 +03:00
805813201e docs: update README 2020-04-27 14:37:38 +03:00
702cd21256 release: 0.2
- fix inplace arg
- follow symlinks by default
- reverse flag
- quiet flag
- try chown/chmod on unix
2020-04-27 14:35:10 +03:00
65 changed files with 445 additions and 137 deletions

118
.github/workflows/binary-release.yml vendored Normal file
View File

@ -0,0 +1,118 @@
name: binary release
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3)
- name: checkout
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: '1.13.0'
- name: Build
run: make -j $(nproc) compile VERSION=${{ steps.get_version.outputs.VERSION }}
- name: create release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.get_version.outputs.VERSION }}
release_name: Release ${{ steps.get_version.outputs.VERSION }}
draft: false
prerelease: false
- name: darwin-386
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-darwin-386
asset_name: ${{ github.event.repository.name }}-darwin-386
asset_content_type: application/octet-stream
- name: darwin-amd64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-darwin-amd64
asset_name: ${{ github.event.repository.name }}-darwin-amd64
asset_content_type: application/octet-stream
- name: freebsd-386
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-freebsd-386
asset_name: ${{ github.event.repository.name }}-freebsd-386
asset_content_type: application/octet-stream
- name: freebsd-amd64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-freebsd-amd64
asset_name: ${{ github.event.repository.name }}-freebsd-amd64
asset_content_type: application/octet-stream
- name: linux-386
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-linux-386
asset_name: ${{ github.event.repository.name }}-linux-386
asset_content_type: application/octet-stream
- name: linux-amd64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-linux-amd64
asset_name: ${{ github.event.repository.name }}-linux-amd64
asset_content_type: application/octet-stream
- name: windows-386
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-windows-386.exe
asset_name: ${{ github.event.repository.name }}-windows-386.exe
asset_content_type: application/octet-stream
- name: windows-amd64
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./bin/${{ github.event.repository.name }}-windows-amd64.exe
asset_name: ${{ github.event.repository.name }}-windows-amd64.exe
asset_content_type: application/octet-stream

23
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: build
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: '1.13.0'
- name: Build
run: make -j $(nproc)

View File

@ -1,21 +1,30 @@
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
NAME = multini
VERSION = dev_$(shell date +%F_%T)
GOCMD = go
LDFLAGS := "$(LDFLAGS) -X 'main.Version=$(VERSION)'"
GOBUILD = $(GOCMD) build -ldflags=$(LDFLAGS)
SRCMAIN = ./cmd/$(NAME)
SRCDOC = ./doc
SRCTEST = ./test
BINDIR = bin
BIN = $(BINDIR)/$(NAME)
README = $(SRCDOC)/README
LICENSE = LICENSE
TEST = $(SRCTEST)/run_test.sh
PREFIX = /usr
.PHONY: all prep doc build check-build freebsd-386 darwin-386 linux-386 windows-386 freebsd-amd64 darwin-amd64 linux-amd64 windows-amd64 compile install check-install uninstall clean test
all: build
prep: clean
go mod init; go mod tidy
mkdir $(BINDIR)
doc: check-build
test -d $(SRCDOC) || mkdir $(SRCDOC)
$(BIN) --help > $(README)
build: prep
$(GOBUILD) -o $(BIN) $(SRCMAIN)
@ -48,7 +57,7 @@ windows-amd64: prep
compile: freebsd-386 darwin-386 linux-386 windows-386 freebsd-amd64 darwin-amd64 linux-amd64 windows-amd64
install: check-build
install: check-build doc
install -m 755 -d $(PREFIX)/bin/
install -m 755 $(BIN) $(PREFIX)/bin/
install -m 755 -d $(PREFIX)/share/licenses/$(NAME)/
@ -71,3 +80,4 @@ clean:
test: check-build
$(TEST) $(BIN)

View File

@ -1,8 +1,8 @@
package main
import (
"multini/output"
"multini/types"
"multini/internal/output"
"multini/internal/types"
)
func chk() int {
@ -22,7 +22,7 @@ func chk() int {
func add(ini *types.Ini) error {
if ArgKeyIsSet {
return ini.AddKey(ArgSection, ArgKey, ArgValue)
return ini.AddKey(ArgSection, ArgKey, ArgValue, ArgReverse)
} else {
ini.AddSection(ArgSection)
return nil

View File

@ -4,8 +4,8 @@ import (
"errors"
"os"
"multini/output"
"multini/types"
"multini/internal/output"
"multini/internal/types"
"github.com/juju/gnuflag"
)
@ -23,6 +23,8 @@ var (
ArgUnix bool
ArgHelp bool
ArgExisting bool
ArgReverse bool
ArgQuiet bool
ArgOutput string
ArgFile string
@ -40,14 +42,15 @@ func printHelp() {
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(" -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(" -r, --reverse For --add, adds an item to the top of the section")
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.")
@ -55,6 +58,7 @@ func printHelp() {
// 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(" -q, --quiet Suppress all normal output")
output.Println(" -h, --help Write this help to stdout")
output.Println(" --version Write version to stdout")
}
@ -74,14 +78,18 @@ func init() {
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(&ArgInplace, "inplace", false, "")
gnuflag.BoolVar(&ArgInplace, "i", false, "")
gnuflag.BoolVar(&ArgUnix, "unix", false, "")
gnuflag.BoolVar(&ArgUnix, "u", false, "")
gnuflag.BoolVar(&ArgWindows, "windows", false, "")
gnuflag.BoolVar(&ArgWindows, "w", false, "")
gnuflag.BoolVar(&ArgReverse, "reverse", false, "")
gnuflag.BoolVar(&ArgReverse, "r", false, "")
gnuflag.BoolVar(&ArgExisting, "existing", false, "")
gnuflag.BoolVar(&ArgExisting, "e", false, "")
gnuflag.BoolVar(&ArgQuiet, "quiet", false, "")
gnuflag.BoolVar(&ArgQuiet, "q", false, "")
gnuflag.BoolVar(&ArgVerbose, "verbose", false, "")
gnuflag.BoolVar(&ArgVerbose, "v", false, "")
gnuflag.StringVar(&ArgOutput, "output", "", "")
@ -92,7 +100,7 @@ func init() {
}
func parseArgs() error {
gnuflag.Parse(true)
gnuflag.Parse(false)
// info
switch {
@ -117,6 +125,7 @@ func parseArgs() error {
// Output settings
output.SetEndOfLineNative()
output.SetVerbose(ArgVerbose)
output.SetQuiet(ArgQuiet)
// Positional Args
for i := 0; i < 4 && i < gnuflag.NArg(); i++ {

View File

@ -1,11 +1,10 @@
package main
import (
"fmt"
"os"
"multini/output"
"multini/types"
"multini/internal/output"
"multini/internal/types"
)
const (
@ -37,7 +36,7 @@ func main() {
ini, err = iniRead(ArgFile)
if err != nil {
fmt.Println(err)
output.Errorln(err)
os.Exit(EXIT_FILE_READ_ERR)
}

View File

@ -7,8 +7,8 @@ import (
"regexp"
"strings"
"multini/output"
"multini/types"
"multini/internal/output"
"multini/internal/types"
)
var (
@ -26,15 +26,15 @@ var (
RxBodyPrefix string = `(?P<` + NgPrefix + `>\s+)?`
RxSectionName string = `\[(?P<` + NgSection + `>.+)\]`
RxKey string = `(?P<` + NgKey + `>(?:[^;#=]+[^\s=;#]|[^;#=]))?`
RxKey string = `(?P<` + NgKey + `>(?:[^;#/=]+[^\s=;#/]|[^;#/=]))?`
RxKeyPostfix string = `(?P<` + NgKeyPostfix + `>\s+)?`
RxValuePrefix string = `(?P<` + NgValuePrefix + `>\s+)?`
RxValue string = `(?P<` + NgValue + `>(?:[^;#]+[^\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*)`
RxCommentPrefix string = `(?P<` + NgCommentPrefix + `>([#;]|//)\s*)`
RxCommentText string = `(?P<` + NgComment + `>.+)?`
RxComment string = `(?:` + RxCommentPrefix + RxCommentText + `)?`
Rx string = RxBodyPrefix + RxBody + RxBodyPostfix + RxComment

17
cmd/multini/stat_unix.go Normal file
View File

@ -0,0 +1,17 @@
// +build !windows
package main
import (
"os"
"syscall"
)
func GetUidGid(info os.FileInfo) (int, int) {
stat, ok := info.Sys().(*syscall.Stat_t)
if ok {
return int(stat.Uid), int(stat.Gid)
} else {
return -1, -1
}
}

View File

@ -0,0 +1,11 @@
// +build windows
package main
import (
"os"
)
func GetUidGid(info os.FileInfo) (int, int) {
return -1, -1
}

127
cmd/multini/writer.go Normal file
View File

@ -0,0 +1,127 @@
package main
import (
"bufio"
"io"
"io/ioutil"
"os"
"path/filepath"
"multini/internal/types"
)
// Source: https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b
/*
GoLang: os.Rename() give error "invalid cross-device link" for Docker container with Volumes.
MoveFile(source, destination) will work moving file between folders
*/
func tryMoveFile(sourcePath, destPath string) error {
inputFile, err := os.Open(sourcePath)
if err != nil {
return err
}
outputFile, err := os.Create(destPath)
if err != nil {
inputFile.Close()
return err
}
defer outputFile.Close()
_, err = io.Copy(outputFile, inputFile)
inputFile.Close()
if err != nil {
return err
}
// The copy was successful, so now delete the original file
err = os.Remove(sourcePath)
if err != nil {
return err
}
return nil
}
func tryRemoveRenameFile(sourcePath, destPath string) bool {
err := os.Remove(destPath)
if err != nil {
return false
}
err = os.Rename(sourcePath, destPath)
if err != nil {
return false
}
return true
}
func replaceOriginal(oldFile, newFile string) error {
realOldFile, err := filepath.EvalSymlinks(oldFile)
if err != nil {
return err
}
infoOldFile, err := os.Stat(realOldFile)
if err != nil {
return err
}
mode := infoOldFile.Mode()
var uid, gid int = GetUidGid(infoOldFile)
if !tryRemoveRenameFile(newFile, realOldFile) {
err = tryMoveFile(newFile, realOldFile)
if err != nil {
return err
}
}
err = os.Chmod(realOldFile, mode)
if err != nil {
return err
}
// try to restore original uid/gid
// don't worry if we can't
os.Chown(realOldFile, uid, gid)
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 {
realfilename, err := filepath.EvalSymlinks(filename)
mode := os.FileMode(int(0644))
if os.IsNotExist(err) {
realfilename = filename
} else if err != nil {
return err
} else {
info, err := os.Stat(realfilename)
if err != nil {
return err
}
mode = info.Mode()
}
targetFile, err := os.OpenFile(realfilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
if err == nil {
datawriter := bufio.NewWriter(targetFile)
_, err = datawriter.WriteString(ini.Full())
if err == nil {
err = datawriter.Flush()
targetFile.Close()
}
}
return err
}

View File

@ -2,21 +2,21 @@ 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.
-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.
-r, --reverse For --add, adds an item to the top of the section
-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
-q, --quiet Suppress all normal output
-h, --help Write this help to stdout
--version Write version to stdout
2020 (c) GenZmeY

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module multini
go 1.13
require github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=

View File

@ -16,6 +16,14 @@ var (
verbose *log.Logger = devNull
)
func SetQuiet(enabled bool) {
if enabled {
stdout = devNull
stderr = devNull
verbose = devNull
}
}
func SetVerbose(enabled bool) {
if enabled {
verbose = stderr

View File

@ -89,6 +89,14 @@ func (obj *Ini) GetKeyVal(section, key, value string) error {
func (obj *Ini) AddSection(section string) *Section {
sect, err := obj.FindSection(section)
if err != nil {
sectSize := len(obj.Sections)
if sectSize > 1 {
prevSect := obj.Sections[sectSize-1].(*Section)
lineSize := len(prevSect.Lines)
if lineSize == 0 || lineSize > 0 && prevSect.Lines[lineSize-1].Type() != TEmptyLine {
obj.Sections[sectSize-1].(*Section).Lines = append(obj.Sections[sectSize-1].(*Section).Lines, &EmptyLine{})
}
}
var newSection Section
newSection.Name = section
newSection.Prefix = obj.Sections[len(obj.Sections)-1].Indent()
@ -102,7 +110,7 @@ func (obj *Ini) SetSection(section string) *Section {
return obj.AddSection(section)
}
func (obj *Ini) AddKey(section, key, value string) error {
func (obj *Ini) AddKey(section, key, value string, reverse bool) error {
sect, err := obj.FindSection(section)
if err != nil {
if createIfNotExist() {
@ -111,7 +119,7 @@ func (obj *Ini) AddKey(section, key, value string) error {
return err
}
}
sect.AddKey(key, value)
sect.AddKey(key, value, reverse)
return nil
}

View File

@ -111,39 +111,54 @@ func (obj *Section) GetKeyVal(name, value string) error {
return errors.New("Parameter:Value not found: " + name + ":" + value)
}
func (obj *Section) appendKey(name, value string) {
func (obj *Section) appendKey(name, value string, reverse bool) {
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
if reverse {
// for right indent and tabs
for i := 0; i < len(obj.Lines); 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
}
}
}
// 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)
obj.Lines = append([]Element{&newKeyValue}, obj.Lines...)
} else {
obj.Lines = append(obj.Lines, obj.Lines[replaceIndex])
obj.Lines[replaceIndex] = &newKeyValue
// 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) {
func (obj *Section) AddKey(name, value string, reverse bool) {
gotIt := false
for i, keyVal := range obj.Lines {
if keyVal.Type() == TKeyValue &&
@ -157,7 +172,7 @@ func (obj *Section) AddKey(name, value string) {
}
}
if !gotIt {
obj.appendKey(name, value)
obj.appendKey(name, value, reverse)
}
}
@ -176,7 +191,7 @@ func (obj *Section) SetKey(name, value string) error {
}
if !gotIt {
if createIfNotExist() {
obj.appendKey(name, value)
obj.appendKey(name, value, false)
} else {
return errors.New("Parameter not found: " + name)
}

View File

@ -10,7 +10,7 @@ DefKey3=NoSpaces!
Key2 = 2
Key3 = 3
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3
@ -17,4 +17,5 @@ DefKey3=NoSpaces!
[SectionWithIndent]
Key=Value
[SectionWithoutNewLineBefore]
[NewSection]

View File

@ -8,7 +8,7 @@ DefKey3=NoSpaces!
[SimpleSection] # Comment For Section
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
[SectionWithIndent]
Key=Value

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = onlyone
[SectionWithIndent]

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -9,7 +9,7 @@ DefKey3=NoSpaces!
Key1 = 1
Key2 = 2
[MultipleKeySection]
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3

View File

@ -1,10 +1,11 @@
#!/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"
TestDir="$ScriptDir/data"
Multini=$(readlink -e "$1")
if [[ -z "$Multini" ]]; then

View File

@ -1,46 +0,0 @@
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
}