release: 0.2

- fix inplace arg
- follow symlinks by default
- reverse flag
- quiet flag
- try chown/chmod on unix
This commit is contained in:
GenZmeY 2020-04-27 14:35:10 +03:00
parent 294a89ba74
commit 702cd21256
10 changed files with 144 additions and 43 deletions

View File

@ -1,5 +1,5 @@
NAME=multini NAME=multini
VERSION=0.1 VERSION=0.2
GOCMD=go GOCMD=go
LDFLAGS:="$(LDFLAGS) -X 'main.Version=$(VERSION)'" LDFLAGS:="$(LDFLAGS) -X 'main.Version=$(VERSION)'"
GOBUILD=$(GOCMD) build -ldflags=$(LDFLAGS) GOBUILD=$(GOCMD) build -ldflags=$(LDFLAGS)

View File

@ -22,7 +22,7 @@ func chk() int {
func add(ini *types.Ini) error { func add(ini *types.Ini) error {
if ArgKeyIsSet { if ArgKeyIsSet {
return ini.AddKey(ArgSection, ArgKey, ArgValue) return ini.AddKey(ArgSection, ArgKey, ArgValue, ArgReverse)
} else { } else {
ini.AddSection(ArgSection) ini.AddSection(ArgSection)
return nil return nil

23
args.go
View File

@ -23,6 +23,8 @@ var (
ArgUnix bool ArgUnix bool
ArgHelp bool ArgHelp bool
ArgExisting bool ArgExisting bool
ArgReverse bool
ArgQuiet bool
ArgOutput string ArgOutput string
ArgFile string ArgFile string
@ -40,14 +42,15 @@ func printHelp() {
output.Println("") output.Println("")
output.Println("Usage: multini [OPTION]... [ACTION] config_file [section] [param] [value]") output.Println("Usage: multini [OPTION]... [ACTION] config_file [section] [param] [value]")
output.Println("Actions:") output.Println("Actions:")
output.Println(" -g, --get get values for a given combination of parameters.") 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(" -s, --set Set values for a given combination of parameters.")
output.Println(" -a, --add add 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(" -d, --del Delete the given combination of parameters.")
output.Println(" -c, --chk display parsing errors for the specified file.") output.Println(" -c, --chk Display parsing errors for the specified file.")
output.Println("") output.Println("")
output.Println("Options:") output.Println("Options:")
output.Println(" -e, --existing For --set and --del, fail if item is missing.") 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(" -i, --inplace Lock and write files in place.")
output.Println(" This is not atomic but has less restrictions") output.Println(" This is not atomic but has less restrictions")
output.Println(" than the default replacement method.") 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(" -v, --verbose Indicate on stderr if changes were made")
output.Println(" -u, --unix Use LF as end of line") output.Println(" -u, --unix Use LF as end of line")
output.Println(" -w, --windows Use CRLF 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(" -h, --help Write this help to stdout")
output.Println(" --version Write version to stdout") output.Println(" --version Write version to stdout")
} }
@ -74,14 +78,18 @@ func init() {
gnuflag.BoolVar(&ArgDel, "d", false, "") gnuflag.BoolVar(&ArgDel, "d", false, "")
gnuflag.BoolVar(&ArgChk, "chk", false, "") gnuflag.BoolVar(&ArgChk, "chk", false, "")
gnuflag.BoolVar(&ArgChk, "c", false, "") gnuflag.BoolVar(&ArgChk, "c", false, "")
gnuflag.BoolVar(&ArgDel, "inplace", false, "") gnuflag.BoolVar(&ArgInplace, "inplace", false, "")
gnuflag.BoolVar(&ArgDel, "i", false, "") gnuflag.BoolVar(&ArgInplace, "i", false, "")
gnuflag.BoolVar(&ArgUnix, "unix", false, "") gnuflag.BoolVar(&ArgUnix, "unix", false, "")
gnuflag.BoolVar(&ArgUnix, "u", false, "") gnuflag.BoolVar(&ArgUnix, "u", false, "")
gnuflag.BoolVar(&ArgWindows, "windows", false, "") gnuflag.BoolVar(&ArgWindows, "windows", false, "")
gnuflag.BoolVar(&ArgWindows, "w", false, "") gnuflag.BoolVar(&ArgWindows, "w", false, "")
gnuflag.BoolVar(&ArgReverse, "reverse", false, "")
gnuflag.BoolVar(&ArgReverse, "r", false, "")
gnuflag.BoolVar(&ArgExisting, "existing", false, "") gnuflag.BoolVar(&ArgExisting, "existing", false, "")
gnuflag.BoolVar(&ArgExisting, "e", false, "") gnuflag.BoolVar(&ArgExisting, "e", false, "")
gnuflag.BoolVar(&ArgQuiet, "quiet", false, "")
gnuflag.BoolVar(&ArgQuiet, "q", false, "")
gnuflag.BoolVar(&ArgVerbose, "verbose", false, "") gnuflag.BoolVar(&ArgVerbose, "verbose", false, "")
gnuflag.BoolVar(&ArgVerbose, "v", false, "") gnuflag.BoolVar(&ArgVerbose, "v", false, "")
gnuflag.StringVar(&ArgOutput, "output", "", "") gnuflag.StringVar(&ArgOutput, "output", "", "")
@ -117,6 +125,7 @@ func parseArgs() error {
// Output settings // Output settings
output.SetEndOfLineNative() output.SetEndOfLineNative()
output.SetVerbose(ArgVerbose) output.SetVerbose(ArgVerbose)
output.SetQuiet(ArgQuiet)
// Positional Args // Positional Args
for i := 0; i < 4 && i < gnuflag.NArg(); i++ { for i := 0; i < 4 && i < gnuflag.NArg(); i++ {

View File

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

View File

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

17
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
}
}

11
stat_windows.go Normal file
View File

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

View File

@ -102,7 +102,7 @@ func (obj *Ini) SetSection(section string) *Section {
return obj.AddSection(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) sect, err := obj.FindSection(section)
if err != nil { if err != nil {
if createIfNotExist() { if createIfNotExist() {
@ -111,7 +111,7 @@ func (obj *Ini) AddKey(section, key, value string) error {
return err return err
} }
} }
sect.AddKey(key, value) sect.AddKey(key, value, reverse)
return nil return nil
} }

View File

@ -111,11 +111,25 @@ func (obj *Section) GetKeyVal(name, value string) error {
return errors.New("Parameter:Value not found: " + name + ":" + value) 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 newKeyValue KeyValue
var replaceIndex int = -1 var replaceIndex int = -1
newKeyValue.Key = name newKeyValue.Key = name
newKeyValue.Value = value newKeyValue.Value = value
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
}
}
obj.Lines = append([]Element{&newKeyValue}, obj.Lines...)
} else {
// replace first emptyline // replace first emptyline
for i := len(obj.Lines) - 1; i >= 0; i-- { for i := len(obj.Lines) - 1; i >= 0; i-- {
if obj.Lines[i].Type() == TEmptyLine { if obj.Lines[i].Type() == TEmptyLine {
@ -142,8 +156,9 @@ func (obj *Section) appendKey(name, value string) {
obj.Lines[replaceIndex] = &newKeyValue obj.Lines[replaceIndex] = &newKeyValue
} }
} }
}
func (obj *Section) AddKey(name, value string) { func (obj *Section) AddKey(name, value string, reverse bool) {
gotIt := false gotIt := false
for i, keyVal := range obj.Lines { for i, keyVal := range obj.Lines {
if keyVal.Type() == TKeyValue && if keyVal.Type() == TKeyValue &&
@ -157,7 +172,7 @@ func (obj *Section) AddKey(name, value string) {
} }
} }
if !gotIt { 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 !gotIt {
if createIfNotExist() { if createIfNotExist() {
obj.appendKey(name, value) obj.appendKey(name, value, false)
} else { } else {
return errors.New("Parameter not found: " + name) return errors.New("Parameter not found: " + name)
} }

View File

@ -4,15 +4,44 @@ import (
"bufio" "bufio"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"multini/types" "multini/types"
) )
func replaceOriginal(oldFile, newFile string) error { func replaceOriginal(oldFile, newFile string) error {
err := os.Remove(oldFile) realOldFile, err := filepath.EvalSymlinks(oldFile)
if err == nil { if err != nil {
err = os.Rename(newFile, oldFile) return err
} }
infoOldFile, err := os.Stat(realOldFile)
if err != nil {
return err
}
mode := infoOldFile.Mode()
var uid, gid int = GetUidGid(infoOldFile)
err = os.Remove(realOldFile)
if err != nil {
return err
}
err = os.Rename(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 return err
} }
@ -33,7 +62,20 @@ func iniWrite(filename string, ini *types.Ini) error {
} }
func iniWriteInplace(filename string, ini *types.Ini) error { func iniWriteInplace(filename string, ini *types.Ini) error {
targetFile, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0644) 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, mode)
if err == nil { if err == nil {
datawriter := bufio.NewWriter(targetFile) datawriter := bufio.NewWriter(targetFile)
_, err = datawriter.WriteString(ini.Full()) _, err = datawriter.WriteString(ini.Full())