snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
270
config/config.go
Normal file
270
config/config.go
Normal file
@@ -0,0 +1,270 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// DEFAULT_SECTION specifies the name of a section if no name provided.
|
||||
DEFAULT_SECTION = "default"
|
||||
// DEFAULT_COMMENT defines what character(s) indicate a comment `#`.
|
||||
DEFAULT_COMMENT = []byte{'#'}
|
||||
// DEFAULT_COMMENT_SEM defines what alternate character(s) indicate a comment `;`.
|
||||
DEFAULT_COMMENT_SEM = []byte{';'}
|
||||
// DEFAULT_MULTI_LINE_SEPARATOR defines what character indicates a multi-line content.
|
||||
DEFAULT_MULTI_LINE_SEPARATOR = []byte{'\\'}
|
||||
)
|
||||
|
||||
// ConfigInterface defines the behavior of a Config implementation.
|
||||
type ConfigInterface interface {
|
||||
String(key string) string
|
||||
Strings(key string) []string
|
||||
Bool(key string) (bool, error)
|
||||
Int(key string) (int, error)
|
||||
Int64(key string) (int64, error)
|
||||
Float64(key string) (float64, error)
|
||||
Set(key string, value string) error
|
||||
}
|
||||
|
||||
// Config represents an implementation of the ConfigInterface.
|
||||
type Config struct {
|
||||
// Section:key=value
|
||||
data map[string]map[string]string
|
||||
}
|
||||
|
||||
// NewConfig create an empty configuration representation from file.
|
||||
func NewConfig(confName string) (ConfigInterface, error) {
|
||||
c := &Config{
|
||||
data: make(map[string]map[string]string),
|
||||
}
|
||||
err := c.parse(confName)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// NewConfigFromText create an empty configuration representation from text.
|
||||
func NewConfigFromText(text string) (ConfigInterface, error) {
|
||||
c := &Config{
|
||||
data: make(map[string]map[string]string),
|
||||
}
|
||||
err := c.parseBuffer(bufio.NewReader(strings.NewReader(text)))
|
||||
return c, err
|
||||
}
|
||||
|
||||
// AddConfig adds a new section->key:value to the configuration.
|
||||
func (c *Config) AddConfig(section string, option string, value string) bool {
|
||||
if section == "" {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if _, ok := c.data[section]; !ok {
|
||||
c.data[section] = make(map[string]string)
|
||||
}
|
||||
|
||||
_, ok := c.data[section][option]
|
||||
c.data[section][option] = value
|
||||
|
||||
return !ok
|
||||
}
|
||||
|
||||
func (c *Config) parse(fname string) (err error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
buf := bufio.NewReader(f)
|
||||
return c.parseBuffer(buf)
|
||||
}
|
||||
|
||||
func (c *Config) parseBuffer(buf *bufio.Reader) error {
|
||||
var section string
|
||||
var lineNum int
|
||||
var buffer bytes.Buffer
|
||||
var canWrite bool
|
||||
for {
|
||||
if canWrite {
|
||||
if err := c.write(section, lineNum, &buffer); err != nil {
|
||||
return err
|
||||
} else {
|
||||
canWrite = false
|
||||
}
|
||||
}
|
||||
lineNum++
|
||||
line, _, err := buf.ReadLine()
|
||||
if err == io.EOF {
|
||||
// force write when buffer is not flushed yet
|
||||
if buffer.Len() > 0 {
|
||||
if err = c.write(section, lineNum, &buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
line = bytes.TrimSpace(line)
|
||||
switch {
|
||||
case bytes.Equal(line, []byte{}), bytes.HasPrefix(line, DEFAULT_COMMENT_SEM),
|
||||
bytes.HasPrefix(line, DEFAULT_COMMENT):
|
||||
canWrite = true
|
||||
continue
|
||||
case bytes.HasPrefix(line, []byte{'['}) && bytes.HasSuffix(line, []byte{']'}):
|
||||
// force write when buffer is not flushed yet
|
||||
if buffer.Len() > 0 {
|
||||
if err := c.write(section, lineNum, &buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
canWrite = false
|
||||
}
|
||||
section = string(line[1 : len(line)-1])
|
||||
default:
|
||||
var p []byte
|
||||
if bytes.HasSuffix(line, DEFAULT_MULTI_LINE_SEPARATOR) {
|
||||
p = bytes.TrimSpace(line[:len(line)-1])
|
||||
p = append(p, " "...)
|
||||
} else {
|
||||
p = line
|
||||
canWrite = true
|
||||
}
|
||||
|
||||
end := len(p)
|
||||
for i, value := range p {
|
||||
if value == DEFAULT_COMMENT[0] || value == DEFAULT_COMMENT_SEM[0] {
|
||||
end = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if _, err := buffer.Write(p[:end]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) write(section string, lineNum int, b *bytes.Buffer) error {
|
||||
if b.Len() <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
optionVal := bytes.SplitN(b.Bytes(), []byte{'='}, 2)
|
||||
if len(optionVal) != 2 {
|
||||
return fmt.Errorf("parse the content error : line %d , %s = ? ", lineNum, optionVal[0])
|
||||
}
|
||||
option := bytes.TrimSpace(optionVal[0])
|
||||
value := bytes.TrimSpace(optionVal[1])
|
||||
c.AddConfig(section, string(option), string(value))
|
||||
|
||||
// flush buffer after adding
|
||||
b.Reset()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bool lookups up the value using the provided key and converts the value to a bool.
|
||||
func (c *Config) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.get(key))
|
||||
}
|
||||
|
||||
// Int lookups up the value using the provided key and converts the value to a int.
|
||||
func (c *Config) Int(key string) (int, error) {
|
||||
return strconv.Atoi(c.get(key))
|
||||
}
|
||||
|
||||
// Int64 lookups up the value using the provided key and converts the value to a int64.
|
||||
func (c *Config) Int64(key string) (int64, error) {
|
||||
return strconv.ParseInt(c.get(key), 10, 64)
|
||||
}
|
||||
|
||||
// Float64 lookups up the value using the provided key and converts the value to a float64.
|
||||
func (c *Config) Float64(key string) (float64, error) {
|
||||
return strconv.ParseFloat(c.get(key), 64)
|
||||
}
|
||||
|
||||
// String lookups up the value using the provided key and converts the value to a string.
|
||||
func (c *Config) String(key string) string {
|
||||
return c.get(key)
|
||||
}
|
||||
|
||||
// Strings lookups up the value using the provided key and converts the value to an array of string
|
||||
// by splitting the string by comma.
|
||||
func (c *Config) Strings(key string) []string {
|
||||
v := c.get(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(v, ",")
|
||||
}
|
||||
|
||||
// Set sets the value for the specific key in the Config.
|
||||
func (c *Config) Set(key string, value string) error {
|
||||
if len(key) == 0 {
|
||||
return errors.New("key is empty")
|
||||
}
|
||||
|
||||
var (
|
||||
section string
|
||||
option string
|
||||
)
|
||||
|
||||
keys := strings.Split(strings.ToLower(key), "::")
|
||||
if len(keys) >= 2 {
|
||||
section = keys[0]
|
||||
option = keys[1]
|
||||
} else {
|
||||
option = keys[0]
|
||||
}
|
||||
|
||||
c.AddConfig(section, option, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// section.key or key.
|
||||
func (c *Config) get(key string) string {
|
||||
var (
|
||||
section string
|
||||
option string
|
||||
)
|
||||
|
||||
keys := strings.Split(strings.ToLower(key), "::")
|
||||
if len(keys) >= 2 {
|
||||
section = keys[0]
|
||||
option = keys[1]
|
||||
} else {
|
||||
section = DEFAULT_SECTION
|
||||
option = keys[0]
|
||||
}
|
||||
|
||||
if value, ok := c.data[section][option]; ok {
|
||||
return value
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
93
config/config_test.go
Normal file
93
config/config_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
config, cerr := NewConfig("testdata/testini.ini")
|
||||
if cerr != nil {
|
||||
t.Errorf("Configuration file loading failed, err:%v", cerr.Error())
|
||||
t.Fatalf("err: %v", cerr)
|
||||
}
|
||||
|
||||
// default::key test
|
||||
if v, err := config.Bool("debug"); err != nil || !v {
|
||||
t.Errorf("Get failure: expected different value for debug (expected: [%#v] got: [%#v])", true, v)
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if v := config.String("url"); v != "act.wiki" {
|
||||
t.Errorf("Get failure: expected different value for url (expected: [%#v] got: [%#v])", "act.wiki", v)
|
||||
}
|
||||
|
||||
// redis::key test
|
||||
if v := config.Strings("redis::redis.key"); len(v) != 2 || v[0] != "push1" || v[1] != "push2" {
|
||||
t.Errorf("Get failure: expected different value for redis::redis.key (expected: [%#v] got: [%#v])", "[]string{push1,push2}", v)
|
||||
}
|
||||
if v := config.String("mysql::mysql.dev.host"); v != "127.0.0.1" {
|
||||
t.Errorf("Get failure: expected different value for mysql::mysql.dev.host (expected: [%#v] got: [%#v])", "127.0.0.1", v)
|
||||
}
|
||||
if v := config.String("mysql::mysql.master.host"); v != "10.0.0.1" {
|
||||
t.Errorf("Get failure: expected different value for mysql::mysql.master.host (expected: [%#v] got: [%#v])", "10.0.0.1", v)
|
||||
}
|
||||
if v := config.String("mysql::mysql.master.user"); v != "root" {
|
||||
t.Errorf("Get failure: expected different value for mysql::mysql.master.user (expected: [%#v] got: [%#v])", "root", v)
|
||||
}
|
||||
if v := config.String("mysql::mysql.master.pass"); v != "89dds)2$" {
|
||||
t.Errorf("Get failure: expected different value for mysql::mysql.master.pass (expected: [%#v] got: [%#v])", "89dds)2$", v)
|
||||
}
|
||||
// math::key test
|
||||
if v, err := config.Int64("math::math.i64"); err != nil || v != 64 {
|
||||
t.Errorf("Get failure: expected different value for math::math.i64 (expected: [%#v] got: [%#v])", 64, v)
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if v, err := config.Float64("math::math.f64"); err != nil || v != 64.1 {
|
||||
t.Errorf("Get failure: expected different value for math::math.f64 (expected: [%#v] got: [%#v])", 64.1, v)
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
_ = config.Set("other::key1", "new test key")
|
||||
|
||||
if v := config.String("other::key1"); v != "new test key" {
|
||||
t.Errorf("Get failure: expected different value for other::key1 (expected: [%#v] got: [%#v])", "new test key", v)
|
||||
}
|
||||
|
||||
_ = config.Set("other::key1", "test key")
|
||||
|
||||
if v := config.String("multi1::name"); v != "r.sub==p.sub && r.obj==p.obj" {
|
||||
t.Errorf("Get failure: expected different value for multi1::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
|
||||
}
|
||||
|
||||
if v := config.String("multi2::name"); v != "r.sub==p.sub && r.obj==p.obj" {
|
||||
t.Errorf("Get failure: expected different value for multi2::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
|
||||
}
|
||||
|
||||
if v := config.String("multi3::name"); v != "r.sub==p.sub && r.obj==p.obj" {
|
||||
t.Errorf("Get failure: expected different value for multi3::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
|
||||
}
|
||||
|
||||
if v := config.String("multi4::name"); v != "" {
|
||||
t.Errorf("Get failure: expected different value for multi4::name (expected: [%#v] got: [%#v])", "", v)
|
||||
}
|
||||
|
||||
if v := config.String("multi5::name"); v != "r.sub==p.sub && r.obj==p.obj" {
|
||||
t.Errorf("Get failure: expected different value for multi5::name (expected: [%#v] got: [%#v])", "r.sub==p.sub&&r.obj==p.obj", v)
|
||||
}
|
||||
}
|
||||
47
config/testdata/testini.ini
vendored
Normal file
47
config/testdata/testini.ini
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# test config
|
||||
debug = true
|
||||
url = act.wiki
|
||||
|
||||
; redis config
|
||||
[redis]
|
||||
redis.key = push1,push2
|
||||
|
||||
; mysql config
|
||||
[mysql]
|
||||
mysql.dev.host = 127.0.0.1
|
||||
mysql.dev.user = root
|
||||
mysql.dev.pass = 123456
|
||||
mysql.dev.db = test
|
||||
|
||||
mysql.master.host = 10.0.0.1 # host # 10.0.0.1
|
||||
mysql.master.user = root ; user name
|
||||
mysql.master.pass = 89dds)2$#d
|
||||
mysql.master.db = act
|
||||
|
||||
; math config
|
||||
[math]
|
||||
math.i64 = 64
|
||||
math.f64 = 64.1
|
||||
|
||||
# multi-line test
|
||||
[multi1]
|
||||
name = r.sub==p.sub \
|
||||
&& r.obj==p.obj\
|
||||
\
|
||||
[multi2]
|
||||
name = r.sub==p.sub \
|
||||
&& r.obj==p.obj
|
||||
|
||||
[multi3]
|
||||
name = r.sub==p.sub \
|
||||
&& r.obj==p.obj
|
||||
|
||||
[multi4]
|
||||
name = \
|
||||
\
|
||||
\
|
||||
|
||||
[multi5]
|
||||
name = r.sub==p.sub \
|
||||
&& r.obj==p.obj\
|
||||
\
|
||||
Reference in New Issue
Block a user