func (c *Ini) HasKey(key string) (ok bool) {
	_, ok = c.GetValue(key)
	return
}
HasKey check key exists
package ini
import (
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
	"sync"
	"github.com/abulo/ratel/v2/config/parser/ini/parser"
	"github.com/pkg/errors"
)
const (
	SepSection = "."
	DefTagName = "ini"
)
var (
	errEmptyKey = errors.New("ini: key name cannot be empty")
	errNotFound = errors.New("ini: key does not exist in the config")
	errReadonly = errors.New("ini: config manager instance in 'readonly' mode")
	dc = New()
)
type Section map[string]string
type Options struct {
	Readonly bool
	ParseEnv bool
	ParseVar bool
	TagName string
	VarOpen string
	VarClose string
	IgnoreCase bool
	DefSection string
	SectionSep string
}
type Ini struct {
	err  error
	opts *Options
	lock sync.RWMutex
	data map[string]Section
	varRegex *regexp.Regexp
}
func New() *Ini {
	return &Ini{
		data: make(map[string]Section),
		opts: newDefaultOptions(),
	}
}
func NewWithOptions(opts ...func(*Options)) *Ini {
	c := New()
	c.WithOptions(opts...)
	return c
}
func Default() *Ini {
	return dc
}
func (c *Ini) ensureInit() {
	if !c.IsEmpty() {
		return
	}
	if c.data == nil {
		c.data = make(map[string]Section)
	}
	if c.opts == nil {
		c.opts = newDefaultOptions()
	}
	if c.opts.ParseVar && c.varRegex == nil {
		l := regexp.QuoteMeta(c.opts.VarOpen)
		r := regexp.QuoteMeta(c.opts.VarClose)
		regStr := l + `([\w-` + c.opts.SectionSep + `]+)` + r
		c.varRegex = regexp.MustCompile(regStr)
	}
}
func newDefaultOptions() *Options {
	return &Options{
		ParseEnv: true,
		VarOpen:  "%(",
		VarClose: ")s",
		TagName:  DefTagName,
		DefSection: parser.DefSection,
		SectionSep: SepSection,
	}
}
func Readonly(opts *Options) {
	opts.Readonly = true
}
func ParseVar(opts *Options) {
	opts.ParseVar = true
}
func ParseEnv(opts *Options) {
	opts.ParseEnv = true
}
func IgnoreCase(opts *Options) {
	opts.IgnoreCase = true
}
func GetOptions() Options {
	return dc.Options()
}
func (c *Ini) Options() Options {
	return *c.opts
}
func WithOptions(opts ...func(*Options)) {
	dc.WithOptions(opts...)
}
func (c *Ini) WithOptions(opts ...func(*Options)) {
	if !c.IsEmpty() {
		panic("ini: Cannot set options after data has been load")
	}
	for _, opt := range opts {
		opt(c.opts)
	}
}
func (c *Ini) DefSection() string {
	return c.opts.DefSection
}
func LoadFiles(files ...string) error { return dc.LoadFiles(files...) }
func (c *Ini) LoadFiles(files ...string) (err error) {
	c.ensureInit()
	for _, file := range files {
		err = c.loadFile(file, false)
		if err != nil {
			return
		}
	}
	return
}
func LoadExists(files ...string) error { return dc.LoadExists(files...) }
func (c *Ini) LoadExists(files ...string) (err error) {
	c.ensureInit()
	for _, file := range files {
		err = c.loadFile(file, true)
		if err != nil {
			return
		}
	}
	return
}
func LoadStrings(strings ...string) error { return dc.LoadStrings(strings...) }
func (c *Ini) LoadStrings(strings ...string) (err error) {
	c.ensureInit()
	for _, str := range strings {
		err = c.parse(str)
		if err != nil {
			return
		}
	}
	return
}
func LoadData(data map[string]Section) error { return dc.LoadData(data) }
func (c *Ini) LoadData(data map[string]Section) (err error) {
	c.ensureInit()
	if len(c.data) == 0 {
		c.data = data
		return
	}
	for name, sec := range data {
		err = c.SetSection(name, sec)
		if err != nil {
			return
		}
	}
	return
}
func (c *Ini) loadFile(file string, loadExist bool) (err error) {
	fd, err := os.Open(file)
	if err != nil {
		if os.IsNotExist(err) && loadExist {
			return nil
		}
		return
	}
	defer fd.Close()
	bts, err := io.ReadAll(fd)
	if err == nil {
		err = c.parse(string(bts))
		if err != nil {
			return
		}
	}
	return
}
func HasKey(key string) bool { return dc.HasKey(key) }