Compare commits

..

No commits in common. "master" and "0.1" have entirely different histories.
master ... 0.1

75 changed files with 229 additions and 876 deletions

View File

@ -1,118 +0,0 @@
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: multini ${{ 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

View File

@ -1,23 +0,0 @@
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,32 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -1,23 +0,0 @@
name: tests
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) && make test

23
.gitignore vendored
View File

@ -1,3 +1,20 @@
/bin/ # Binaries for programs and plugins
/test/data/out_ini/ bin/*
/cmd/multini/multini 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/

View File

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

View File

@ -1,22 +1,22 @@
A utility for manipulating ini files with duplicate keys A utility for manipulating ini files with duplicate keys
Usage: multini [OPTION]... [ACTION] config_file [section] [key] [value] Usage: multini [OPTION]... [ACTION] config_file [section] [param] [value]
Actions: Actions:
-g, --get Get values for a given combination of parameters. -g, --get get values for a given combination of parameters.
-s, --set Set 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. -a, --add add values for a given combination of parameters.
-d, --del Delete the given combination of parameters. -d, --del delete the given combination of parameters.
-c, --chk Display parsing errors for the specified file. -c, --chk display parsing errors for the specified file.
Options: Options:
-e, --existing For --set and --del, fail if item is missing. -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. -i, --inplace Lock and write files in place.
This is not atomic but has less restrictions This is not atomic but has less restrictions
than the default replacement method. than the default replacement method.
-o, --output FILE Write output to FILE instead. '-' means stdout -o, --output FILE Write output to FILE instead. '-' means stdout
-u, --unix Use LF as end of line -u, --unix Use LF as end of line
-w, --windows Use CRLF 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 -h, --help Write this help to stdout
--version Write version to stdout --version Write version to stdout
2020 (c) GenZmeY

View File

@ -1,111 +0,0 @@
# Multini
[![build](https://github.com/GenZmeY/multini/workflows/build/badge.svg)](https://github.com/GenZmeY/multini/actions?query=workflow%3Abuild)
[![tests](https://github.com/GenZmeY/multini/workflows/tests/badge.svg)](https://github.com/GenZmeY/multini/actions?query=workflow%3Atests)
[![CodeQL](https://github.com/GenZmeY/multini/workflows/CodeQL/badge.svg)](https://github.com/GenZmeY/multini/security/code-scanning)
[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/GenZmeY/multini)](https://golang.org)
[![GitHub](https://img.shields.io/github/license/genzmey/multini)](LICENSE)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/GenZmeY/multini)](https://github.com/GenZmeY/multini/releases)
[English](README.md), [Русский](README-ru.md)
*Утилита командной строки для манипулирования ini файлами с дублирующимися именами ключей.*
Скомпилированная версия multini доступна на [странице релизов](https://github.com/GenZmeY/multini/releases).
***
# Описание
Некоторые программы используют формат ini файлов допускающий повторяющиеся имена ключей.
Например игры основаные на [unreal engine](https://en.wikipedia.org/wiki/Unreal_Engine).
Это может выглядеть так (часть конфигурации Killing Floor 2):
```
[OnlineSubsystemSteamworks.KFWorkshopSteamworks]
ServerSubscribedWorkshopItems=2267561023
ServerSubscribedWorkshopItems=2085786712
ServerSubscribedWorkshopItems=2222630586
ServerSubscribedWorkshopItems=2146677560
```
Большинство реализаций поддерживают только одно свойство с заданным именем в секции. Если их несколько, будет обрабатываться только первый (или последний) ключ, чего в данном случае недостаточно. multini решает эту проблему.
**примечание:**
- multini чувствителен к регистру;
- кавычки вокруг значения не обрабатываются (multini считает их частью значения);
- многострочные значения не поддерживаются.
(но все это может измениться в будущем)
# Сборка и установка (вручную)
1. Установите [golang](https://golang.org), [git](https://git-scm.com/), [make](https://www.gnu.org/software/make/);
2. Клонируйте этот репозиторий: `git clone https://github.com/GenZmeY/multini`
3. Перейдите в каталог с исходниками: `cd multini`
4. Выполните сборку: `make`
5. Выполните установку: `make install`
# Использование
```
Использование: multini [ПАРАМЕТРЫ]... ДЕЙСТВИЕ ini_file [секция] [ключ] [значение]
Действия:
-g, --get Получить значения для заданной комбинации параметров.
-s, --set Установить значения для заданной комбинации параметров.
-a, --add Добавить значения для заданной комбинации параметров.
-d, --del Удалить указанную комбинацию параметров.
-c, --chk Показать ошибки парсинга указанного файла.
Параметры:
-e, --existing Для --set и --del завершить программу с ошибкой, если элемент остутствует.
-r, --reverse Для --add добавлять элемент в начало секции
-i, --inplace Перезаписывать исходный файл.
Это не атомарно, но требует меньше разрешений
чем способ по умолчанию с заменой файла.
-o, --output ФАЙЛ Записать результат в ФАЙЛ. '-' означает стандартный вывод
-u, --unix Использовать LF в конце строки
-w, --windows Использовать CRLF в конце строки
-q, --quiet Подавить весь вывод
-h, --help Отобразить страницу помощи
--version Отобразить версию
```
# Примеры
**вывести глобальное значение вне секции:**
`multini --get ini_file '' key`
**вывести секцию:**
`multini --get ini_file section`
**вывести список секций:**
`multini --get ini_file`
**вывести значение:**
`multini --get ini_file section key`
- если ключей несколько, отобразится список всех значений этих ключей
**создать/обновить ключ (в единственном экземпляре):**
`multini --set ini_file section key value`
- если ключа нет, он будет добавлен
- если ключ существует, значение будет обновлено
- если ключ существует и имеет несколько значений, будет установлен ключ с указанным значением, остальные значения будут удалены
**добавить ключ с указанным значением:**
`multini --add ini_file section key value`
- если ключа нет, он будет добавлен
- если ключ существует и не имеет указанного значения, будет добавлено новое значение
- если указанное значение повторяет существующее, никаких изменений не будет
**удалить все ключи с указанным именем:**
`multini --del ini_file section key`
**удалить ключ с указанным именем и значением:**
`multini --del ini_file section key value`
**удалить секцию:**
`multini --del ini_file section`
**короткие версии параметров можно комбинировать:**
`multini -gq ini_file section key value`
- проверить наличие ключа с заданным значением, используя код возврата
# Лицензия
Copyright © 2020 GenZmeY
[MIT License](LICENSE).

111
README.md
View File

@ -1,111 +0,0 @@
# Multini
[![build](https://github.com/GenZmeY/multini/workflows/build/badge.svg)](https://github.com/GenZmeY/multini/actions?query=workflow%3Abuild)
[![tests](https://github.com/GenZmeY/multini/workflows/tests/badge.svg)](https://github.com/GenZmeY/multini/actions?query=workflow%3Atests)
[![CodeQL](https://github.com/GenZmeY/multini/workflows/CodeQL/badge.svg)](https://github.com/GenZmeY/multini/security/code-scanning)
[![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/GenZmeY/multini)](https://golang.org)
[![GitHub](https://img.shields.io/github/license/genzmey/multini)](LICENSE)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/GenZmeY/multini)](https://github.com/GenZmeY/multini/releases)
[English](README.md), [Русский](README-ru.md)
*Command line utility for manipulating ini files with duplicate key names.*
A compiled version of multini is available on the [release page](https://github.com/GenZmeY/multini/releases).
***
# Description
Some programs use ini file format with duplicate key names.
For example, these are games based on the [unreal engine](https://en.wikipedia.org/wiki/Unreal_Engine).
It might look like this (part of the Killing Floor 2 config):
```
[OnlineSubsystemSteamworks.KFWorkshopSteamworks]
ServerSubscribedWorkshopItems=2267561023
ServerSubscribedWorkshopItems=2085786712
ServerSubscribedWorkshopItems=2222630586
ServerSubscribedWorkshopItems=2146677560
```
Most implementations only support having one property with a given name in a section. If there are several of them, only the first (or last) key will be processed, which is not enough in this case. multini solves this problem.
**note:**
- multini is case sensitive;
- quotes around the value are not processed (they are part of the value for multini);
- multi-line values are not supported.
(but this may change in the future)
# Build & Install (Manual)
1. Install [golang](https://golang.org), [git](https://git-scm.com/), [make](https://www.gnu.org/software/make/);
2. Clone this repo: `git clone https://github.com/GenZmeY/multini`
3. Go to the source directory: `cd multini`
4. Build: `make`
5. Install: `make install`
# Usage
```
Usage: multini [OPTION]... ACTION ini_file [section] [key] [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.
-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
```
# Examples
**output a global value not in a section:**
`multini --get ini_file '' key`
**output section:**
`multini --get ini_file section`
**output list of existing sections:**
`multini --get ini_file`
**output value:**
`multini --get ini_file section key`
- if there are several keys, a list of all values of this keys will be displayed
**create/update a single key:**
`multini --set ini_file section key value`
- if there is no key, it will be added
- if the key exists, the value will be updated
- if the key exists and has several values, the key with the specified value will be set, the rest of the values will be deleted
**add a key with specified value:**
`multini --add ini_file section key value`
- if there is no key, it will be added
- if the key exists and does not have the specified value, the new value will be added
- if the specified value repeats the existing one, no changes will be made
**delete all keys with specified name:**
`multini --del ini_file section key`
**delete a key with specified name and value:**
`multini --del ini_file section key value`
**delete a section:**
`multini --del ini_file section`
**short options can be combined:**
`multini -gq ini_file section key value`
- check the existence of a key with a given value using the return code
# License
Copyright © 2020 GenZmeY
The content of this repository is licensed under [MIT License](LICENSE).

View File

@ -1,8 +1,8 @@
package main package main
import ( import (
"multini/internal/output" "multini/output"
"multini/internal/types" "multini/types"
) )
func chk() int { func chk() int {
@ -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, ArgReverse) return ini.AddKey(ArgSection, ArgKey, ArgValue)
} else { } else {
ini.AddSection(ArgSection) ini.AddSection(ArgSection)
return nil return nil

View File

@ -4,8 +4,8 @@ import (
"errors" "errors"
"os" "os"
"multini/internal/output" "multini/output"
"multini/internal/types" "multini/types"
"github.com/juju/gnuflag" "github.com/juju/gnuflag"
) )
@ -23,8 +23,6 @@ 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,24 +38,23 @@ var (
func printHelp() { func printHelp() {
output.Println("A utility for manipulating ini files with duplicate keys") output.Println("A utility for manipulating ini files with duplicate keys")
output.Println("") output.Println("")
output.Println("Usage: multini [OPTION]... [ACTION] config_file [section] [key] [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.")
output.Println(" -o, --output FILE Write output to FILE instead. '-' means stdout") 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(" -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")
} }
@ -77,18 +74,14 @@ 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(&ArgInplace, "inplace", false, "") gnuflag.BoolVar(&ArgDel, "inplace", false, "")
gnuflag.BoolVar(&ArgInplace, "i", false, "") gnuflag.BoolVar(&ArgDel, "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", "", "")
@ -99,7 +92,7 @@ func init() {
} }
func parseArgs() error { func parseArgs() error {
gnuflag.Parse(false) gnuflag.Parse(true)
// info // info
switch { switch {
@ -124,7 +117,6 @@ 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,17 +0,0 @@
// +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

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

View File

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

5
go.mod
View File

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

2
go.sum
View File

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

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

View File

@ -16,14 +16,6 @@ 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

View File

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

View File

@ -1,11 +1,10 @@
#!/bin/bash #!/bin/bash
DEF='\e[0m'; BLD='\e[1m'; RED='\e[31m'; GRN='\e[32m'; WHT='\e[97m' DEF='\e[0m'; BLD='\e[1m'; RED='\e[31m'; GRN='\e[32m'; WHT='\e[97m'
ScriptFullname=$(readlink -e "$0") ScriptFullname=$(readlink -e "$0")
ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}') ScriptName=$(echo "$ScriptFullname" | awk -F '/' '{print $NF;}')
ScriptDir=$(dirname "$ScriptFullname") ScriptDir=$(dirname "$ScriptFullname")
TestDir="$ScriptDir/data" TestDir="$ScriptDir/tests"
Multini=$(readlink -e "$1") Multini=$(readlink -e "$1")
if [[ -z "$Multini" ]]; then if [[ -z "$Multini" ]]; then

View File

@ -1,20 +0,0 @@
# comment
; comment again
// comment with indent
DefKey1 = Some Value1
DefKey2 = Some Value2 And Tabs! # With Comment
DefKey3=NoSpaces!
[SimpleSection] # Comment For Section
Key1 = 1
Key2 = 2
Key3 = 3
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3
[SectionWithIndent]
Key=Value
[SectionWithoutNewLineBefore]

View File

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

View File

@ -1,19 +0,0 @@
# comment
; comment again
// comment with indent
DefKey1 = Some Value1
DefKey2 = Some Value2 And Tabs! # With Comment
DefKey3=NoSpaces!
[SimpleSection] # Comment For Section
Key1 = 1
Key2 = 2
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3
[SectionWithIndent]
Key=Value
[SectionWithoutNewLineBefore]

View File

@ -1,19 +0,0 @@
# comment
; comment again
DefKey1 = Some Value1
DefKey2 = Some Value2 And Tabs! # With Comment
DefKey3=NoSpaces!
[Slashes/Test] // Comment For Section
./Dir1/File = skip/skip // comment
Key2 = 2
[MultipleKeySection] // C style comment
Key = 1
Key = 2
Key = 3
[SectionWithIndent]
Key=Value
[SectionWithoutNewLineBefore]

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -89,14 +89,6 @@ func (obj *Ini) GetKeyVal(section, key, value string) error {
func (obj *Ini) AddSection(section string) *Section { func (obj *Ini) AddSection(section string) *Section {
sect, err := obj.FindSection(section) sect, err := obj.FindSection(section)
if err != nil { 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 var newSection Section
newSection.Name = section newSection.Name = section
newSection.Prefix = obj.Sections[len(obj.Sections)-1].Indent() newSection.Prefix = obj.Sections[len(obj.Sections)-1].Indent()
@ -110,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, reverse bool) error { func (obj *Ini) AddKey(section, key, value string) error {
sect, err := obj.FindSection(section) sect, err := obj.FindSection(section)
if err != nil { if err != nil {
if createIfNotExist() { if createIfNotExist() {
@ -119,7 +111,7 @@ func (obj *Ini) AddKey(section, key, value string, reverse bool) error {
return err return err
} }
} }
sect.AddKey(key, value, reverse) sect.AddKey(key, value)
return nil return nil
} }

View File

@ -111,25 +111,11 @@ 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, reverse bool) { func (obj *Section) appendKey(name, value string) {
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 {
@ -156,9 +142,8 @@ func (obj *Section) appendKey(name, value string, reverse bool) {
obj.Lines[replaceIndex] = &newKeyValue obj.Lines[replaceIndex] = &newKeyValue
} }
} }
}
func (obj *Section) AddKey(name, value string, reverse bool) { func (obj *Section) AddKey(name, value string) {
gotIt := false gotIt := false
for i, keyVal := range obj.Lines { for i, keyVal := range obj.Lines {
if keyVal.Type() == TKeyValue && if keyVal.Type() == TKeyValue &&
@ -172,7 +157,7 @@ func (obj *Section) AddKey(name, value string, reverse bool) {
} }
} }
if !gotIt { if !gotIt {
obj.appendKey(name, value, reverse) obj.appendKey(name, value)
} }
} }
@ -191,7 +176,7 @@ func (obj *Section) SetKey(name, value string) error {
} }
if !gotIt { if !gotIt {
if createIfNotExist() { if createIfNotExist() {
obj.appendKey(name, value, false) obj.appendKey(name, value)
} else { } else {
return errors.New("Parameter not found: " + name) return errors.New("Parameter not found: " + name)
} }

46
writer.go Normal file
View 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
}