ini - INI Configuration File Parser
ini is a powerful INI configuration file parser based on go-ini, with enhanced environment variable injection functionality. It supports multiple data sources, type conversion, variable references, environment variable substitution, and other advanced features.
Installation
go get -u go-slim.dev/iniQuick Start
Basic Usage
package mainimport ( "fmt" "log" "go-slim.dev/ini")func main() { // Create INI manager and load file cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { log.Fatal(err) } // Read configuration values section := cfg.Section("database") host := section.String("host") port := section.MustInt("port", 3306) fmt.Printf("Database: %s:%d\n", host, port)}INI File Example
# config.ini# Application configuration[app]name = MyAppdebug = trueport = 8080# Database configuration[database]host = localhostport = 3306username = rootpassword = secretmax_connections = 100# Redis configuration[redis]host = localhostport = 6379database = 0Core Concepts
1. Manager
Manager is the core management object for INI configuration, responsible for loading, parsing, and managing all configuration data.
2. Section
Section represents a configuration section in the INI file (marked with [section_name]).
3. Key
Key represents a key-value pair within a configuration section.
Hierarchical Structure
Manager └── Section 1 ├── Key 1 ├── Key 2 └── ... └── Section 2 ├── Key 1 └── ...Core Features
1. Creating a Manager
New - Create New Manager
func New(opts Options) *ManagerDescription:
Creates a new INI manager with the specified options.
Parameters:
opts: Configuration options (see Options description)
Example:
// Use default optionscfg := ini.New(ini.Options{})// Custom optionscfg := ini.New(ini.Options{ Insensitive: true, // Case-insensitive key names AllowBooleanKeys: true, // Allow boolean type keys IgnoreInlineComment: false, // Do not ignore inline comments})2. Loading Data Sources
Append - Append Data Source
func (m *Manager) Append(source any, others ...any) errorDescription:
Appends one or more data sources and automatically reloads. Supports multiple data source types.
Supported Data Source Types:
string: File path[]byte: Byte sliceio.Reader: Readerio.ReadCloser: Closable readerDataSource: Custom data source interfacefunc() (io.ReadCloser, error): Data source factory function
Example:
cfg := ini.New(ini.Options{})// Load from filecfg.Append("config.ini")// Load from multiple filescfg.Append("base.ini", "production.ini", "local.ini")// Load from byte slicedata := []byte(`[app]name = MyAppport = 8080`)cfg.Append(data)// Load from io.Readerfile, _ := os.Open("config.ini")cfg.Append(file)Batch - Batch Operations
func (m *Manager) Batch(fn func(m *Manager) error) errorDescription:
Executes multiple operations in batch without triggering auto-reload during execution. All operations are loaded together after completion.
Example:
cfg := ini.New(ini.Options{})err := cfg.Batch(func(m *ini.Manager) error { // Load multiple files in batch, reload only once at the end if err := m.Append("base.ini"); err != nil { return err } if err := m.Append("override.ini"); err != nil { return err } return nil})Reload - Reload
func (m *Manager) Reload() errorDescription:
Reloads and reparses all data sources. Clears existing data and reparses.
Example:
// Reload after modifying configuration fileif err := cfg.Reload(); err != nil { log.Fatal(err)}3. Section Operations
Section - Get Section
func (m *Manager) Section(name string) *SectionDescription:
Gets the section with the specified name. Returns a zero-value section if it doesn’t exist (no error).
Example:
// Get sectiondbSection := cfg.Section("database")appSection := cfg.Section("app")// Default section (part without section name at the beginning of the file)defaultSection := cfg.Section("")GetSection - Get Section (with Error Check)
func (m *Manager) GetSection(name string) (*Section, error)Description:
Gets the section with the specified name. Returns an error if it doesn’t exist.
Example:
section, err := cfg.GetSection("database")if err != nil { log.Printf("Section not found: %v", err) return}HasSection - Check if Section Exists
func (m *Manager) HasSection(name string) boolDescription:
Checks if the specified section exists.
Example:
if cfg.HasSection("database") { // Database configuration exists}NewSection - Create New Section
func (m *Manager) NewSection(name string) *SectionDescription:
Creates a new section. Returns the existing section if it already exists.
Example:
// Create new sectionsection := cfg.NewSection("cache")section.NewKey("driver", "redis")section.NewKey("host", "localhost")4. Key Operations
Key - Get Key
func (s *Section) Key(name string) *KeyDescription:
Gets the key with the specified name. Returns a zero-value key if it doesn’t exist (no error).
Example:
section := cfg.Section("database")hostKey := section.Key("host")portKey := section.Key("port")GetKey - Get Key (with Error Check)
func (s *Section) GetKey(name string) (*Key, error)Description:
Gets the key with the specified name. Returns an error if it doesn’t exist.
Supports Sub-section Lookup: If the key is not found in the current section, it will look up in the parent section.
Example:
section := cfg.Section("database")key, err := section.GetKey("host")if err != nil { log.Printf("Key not found: %v", err) return}HasKey - Check if Key Exists
func (s *Section) HasKey(name string) boolExample:
section := cfg.Section("database")if section.HasKey("password") { // Password configuration exists}NewKey - Create New Key
func (s *Section) NewKey(name, value string) *KeyDescription:
Creates a new key-value pair in the section. Returns the existing key if it already exists.
Example:
section := cfg.NewSection("app")section.NewKey("name", "MyApp")section.NewKey("version", "1.0.0")Keys - Get All Keys
func (s *Section) Keys() []*KeyDescription:
Returns all keys in the section.
Example:
section := cfg.Section("database")for _, key := range section.Keys() { fmt.Printf("%s = %s\n", key.Name(), key.String())}5. Value Reading - Basic Types
String - Get String Value
func (s *Section) String(name string) stringfunc (k *Key) String() stringExample:
// Read from Sectionhost := cfg.Section("database").String("host")// Read from Keykey := cfg.Section("database").Key("host")value := key.String()MustString - Get String Value (with Default)
func (s *Section) MustString(name string, defaultVal ...string) stringfunc (k *Key) MustString(defaultVal string) stringExample:
// Returns default if key doesn't exist or is emptyhost := cfg.Section("database").MustString("host", "localhost")Int/Int64/Uint/Uint64 - Get Integer Values
func (s *Section) Int(name string) (int, error)func (s *Section) Int64(name string) (int64, error)func (s *Section) Uint(name string) (uint, error)func (s *Section) Uint64(name string) (uint64, error)Example:
port, err := cfg.Section("database").Int("port")if err != nil { log.Printf("Invalid port: %v", err)}maxConns, _ := cfg.Section("database").Uint("max_connections")MustInt/MustInt64/MustUint/MustUint64 - Get Integer Values (with Default)
func (s *Section) MustInt(name string, defaultVal ...int) intfunc (s *Section) MustInt64(name string, defaultVal ...int64) int64func (s *Section) MustUint(name string, defaultVal ...uint) uintfunc (s *Section) MustUint64(name string, defaultVal ...uint64) uint64Example:
// Returns default if conversion fails or doesn't existport := cfg.Section("app").MustInt("port", 8080)timeout := cfg.Section("server").MustInt64("timeout", 30)maxSize := cfg.Section("upload").MustUint("max_size", 1024)Float64 - Get Float Value
func (s *Section) Float64(name string) (float64, error)func (s *Section) MustFloat64(name string, defaultVal ...float64) float64Example:
rate, err := cfg.Section("app").Float64("tax_rate")ratio := cfg.Section("app").MustFloat64("compression_ratio", 0.8)Bool - Get Boolean Value
func (s *Section) Bool(name string) (bool, error)func (s *Section) MustBool(name string, defaultVal ...bool) boolSupported Boolean Values:
true: “1”, “t”, “T”, “true”, “TRUE”, “True”, “YES”, “yes”, “Yes”, “y”, “ON”, “on”, “On”false: “0”, “f”, “F”, “false”, “FALSE”, “False”, “NO”, “no”, “No”, “n”, “OFF”, “off”, “Off”
Example:
debug, err := cfg.Section("app").Bool("debug")verbose := cfg.Section("app").MustBool("verbose", false)Duration - Get Time Duration
func (s *Section) Duration(name string) (time.Duration, error)func (s *Section) MustDuration(name string, defaultVal ...time.Duration) time.DurationExample:
// config.ini// [server]// timeout = 30s// interval = 5mtimeout, err := cfg.Section("server").Duration("timeout")interval := cfg.Section("server").MustDuration("interval", 1*time.Minute)Time - Get Time Value
func (s *Section) Time(name string) (time.Time, error)func (s *Section) TimeFormat(name string, format string) (time.Time, error)func (s *Section) MustTime(name string, defaultVal ...time.Time) time.Timefunc (s *Section) MustTimeFormat(name string, format string, defaultVal ...time.Time) time.TimeExample:
// config.ini// [app]// created_at = 2023-12-25T10:30:45Z// updated_at = 2023/12/25 10:30:45// RFC3339 formatcreatedAt, err := cfg.Section("app").Time("created_at")// Custom formatupdatedAt, err := cfg.Section("app").TimeFormat("updated_at", "2006/01/02 15:04:05")// With defaultdefaultTime := time.Now()lastLogin := cfg.Section("user").MustTime("last_login", defaultTime)6. List Value Reading
Strings - Get String List
func (s *Section) Strings(name string, delim string) []stringfunc (k *Key) Strings(delim string) []stringDescription:
Splits the value using the specified delimiter and returns a string list.
Example:
// config.ini// [app]// allowed_origins = http://localhost:3000,https://example.com,https://api.example.com// tags = go, web, apiorigins := cfg.Section("app").Strings("allowed_origins", ",")// []string{"http://localhost:3000", "https://example.com", "https://api.example.com"}tags := cfg.Section("app").Strings("tags", ",")// []string{"go", "web", "api"} (auto-trimmed spaces)Ints/Int64s/Uints/Uint64s - Get Integer Lists
func (s *Section) Ints(name string, delim string) []intfunc (s *Section) Int64s(name string, delim string) []int64func (s *Section) Uints(name string, delim string) []uintfunc (s *Section) Uint64s(name string, delim string) []uint64Description:
Splits the value and converts to an integer list. Invalid values are converted to zero.
Example:
// config.ini// [app]// ports = 8080,8081,8082// retry_delays = 1,2,5,10ports := cfg.Section("app").Ints("ports", ",")// []int{8080, 8081, 8082}delays := cfg.Section("app").Int64s("retry_delays", ",")// []int64{1, 2, 5, 10}ValidInts/ValidInt64s - Get Valid Integer Lists
func (s *Section) ValidInts(name string, delim string) []intfunc (s *Section) ValidInt64s(name string, delim string) []int64Description:
Splits the value and converts to an integer list. Invalid values are skipped (not included in the result).
Example:
// config.ini// [app]// ports = 8080,invalid,8081,8082ports := cfg.Section("app").Ints("ports", ",")// []int{8080, 0, 8081, 8082} (invalid converted to 0)validPorts := cfg.Section("app").ValidInts("ports", ",")// []int{8080, 8081, 8082} (invalid skipped)StrictInts/StrictInt64s - Get Strict Integer Lists
func (s *Section) StrictInts(name string, delim string) ([]int, error)func (s *Section) StrictInt64s(name string, delim string) ([]int64, error)Description:
Splits the value and converts to an integer list. Returns an error on the first invalid value.
Example:
// config.ini// [app]// ports = 8080,8081,8082// bad_ports = 8080,invalid,8082ports, err := cfg.Section("app").StrictInts("ports", ",")// []int{8080, 8081, 8082}, nilbadPorts, err := cfg.Section("app").StrictInts("bad_ports", ",")// nil, error (returns error when encountering "invalid")Float64s/Bools/Times - Other Type Lists
// Float listsfunc (s *Section) Float64s(name string, delim string) []float64func (s *Section) ValidFloat64s(name string, delim string) []float64func (s *Section) StrictFloat64s(name string, delim string) ([]float64, error)// Boolean listsfunc (s *Section) Bools(name string, delim string) []boolfunc (s *Section) ValidBools(name string, delim string) []boolfunc (s *Section) StrictBools(name string, delim string) ([]bool, error)// Time listsfunc (s *Section) Times(name string, delim string) []time.Timefunc (s *Section) TimesFormat(name string, format, delim string) []time.Timefunc (s *Section) ValidTimes(name string, delim string) []time.Timefunc (s *Section) StrictTimes(name string, delim string) ([]time.Time, error)Example:
// config.ini// [app]// rates = 0.1,0.2,0.3// features = true,false,true,false// timestamps = 2023-01-01T00:00:00Z,2023-06-01T00:00:00Zrates := cfg.Section("app").Float64s("rates", ",")features := cfg.Section("app").Bools("features", ",")timestamps := cfg.Section("app").Times("timestamps", ",")7. Value Validation and Range Checking
In - Check if Value is in Candidate List
func (s *Section) In(name string, defaultVal string, candidates []string) stringfunc (s *Section) InInt(name string, defaultVal int, candidates []int) intfunc (s *Section) InFloat64(name string, defaultVal float64, candidates []float64) float64Description:
Checks if the value is in the given candidate list. Returns the default if not.
Example:
// config.ini// [app]// log_level = debug// port = 8080// Only allow specific log levelslogLevel := cfg.Section("app").In("log_level", "info", []string{"debug", "info", "warn", "error"})// Returns "debug"invalidLevel := cfg.Section("app").In("invalid_level", "info", []string{"debug", "info", "warn", "error"})// Key doesn't exist, returns default "info"// Only allow specific portsport := cfg.Section("app").InInt("port", 8080, []int{8080, 8081, 8082})// Returns 8080Range - Range Check
func (s *Section) RangeInt(name string, defaultVal, min, max int) intfunc (s *Section) RangeInt64(name string, defaultVal, min, max int64) int64func (s *Section) RangeFloat64(name string, defaultVal, min, max float64) float64func (s *Section) RangeTime(name string, defaultVal, min, max time.Time) time.TimeDescription:
Checks if the value is within the given range (inclusive). Returns the default if out of range.
Example:
// config.ini// [server]// port = 8080// workers = 4// rate_limit = 0.95// Port must be between 1024-65535port := cfg.Section("server").RangeInt("port", 8080, 1024, 65535)// Returns 8080 (within range)badPort := cfg.Section("server").RangeInt("bad_port", 8080, 1024, 65535)// Assuming bad_port = 100, returns default 8080 (out of range)// Worker count must be between 1-16workers := cfg.Section("server").RangeInt("workers", 4, 1, 16)// Rate limit must be between 0.0-1.0rateLimit := cfg.Section("server").RangeFloat64("rate_limit", 0.8, 0.0, 1.0)Validate - Custom Validation
func (s *Section) Validate(name string, fn func(string) string) stringfunc (k *Key) Validate(fn func(string) string) stringDescription:
Validates and potentially modifies the key value using a custom function.
Example:
// config.ini// [app]// url = HTTP://EXAMPLE.COM/API// Convert to lowercaseurl := cfg.Section("app").Validate("url", func(value string) string { return strings.ToLower(value)})// Returns "http://example.com/api"// Ensure ends with slashapiBase := cfg.Section("app").Validate("api_base", func(value string) string { if !strings.HasSuffix(value, "/") { return value + "/" } return value})8. Variable References and Environment Variables
Variable References
INI files support referencing other key values using the %(key)s syntax.
Example:
# config.ini[paths]root = /var/wwwdata = %(root)s/datalogs = %(root)s/logscache = %(root)s/cache[database]host = localhostport = 3306dsn = %(host)s:%(port)scfg := ini.New(ini.Options{})cfg.Append("config.ini")// Automatically resolves referencesdataPath := cfg.Section("paths").String("data")// Returns "/var/www/data"logsPath := cfg.Section("paths").String("logs")// Returns "/var/www/logs"dsn := cfg.Section("database").String("dsn")// Returns "localhost:3306"Cross-Section References:
If the referenced key is not found in the current section, it will automatically look in the default section (unnamed section):
# config.ini# Default sectionroot = /var/www[app]data_dir = %(root)s/datadataDir := cfg.Section("app").String("data_dir")// Returns "/var/www/data" (root retrieved from default section)Environment Variable Injection
This is the main enhancement of go-slim.dev/ini compared to the original go-ini.
Supports two syntax patterns for injecting environment variables:
Syntax 1: ${VAR||default} - Empty Fallback
Uses the default when the environment variable doesn’t exist or is empty.
Syntax 2: ${VAR??default} - Non-existent Fallback
Uses the default only when the environment variable doesn’t exist (empty strings are considered valid).
Example:
# config.ini[database]# Read from environment variable, use default if doesn't existhost = ${DB_HOST||localhost}port = ${DB_PORT||3306}username = ${DB_USER||root}password = ${DB_PASSWORD}[app]# Force fallback: use default even if env var exists but is emptyapi_key = ${API_KEY||default_key}# Weak fallback: use default only if doesn't existfeature_flag = ${FEATURE_FLAG??true}// Set environment variablesos.Setenv("DB_HOST", "prod-db.example.com")os.Setenv("DB_PORT", "5432")os.Setenv("FEATURE_FLAG", "") // Empty stringcfg := ini.New(ini.Options{})cfg.Append("config.ini")// Read configurationhost := cfg.Section("database").String("host")// Returns "prod-db.example.com" (from environment variable)port := cfg.Section("database").MustInt("port", 3306)// Returns 5432 (from environment variable)username := cfg.Section("database").String("username")// Returns "root" (env var not set, use default)// || vs ??apiKey := cfg.Section("app").String("api_key")// If API_KEY is empty string, returns "default_key"featureFlag := cfg.Section("app").String("feature_flag")// Returns "" (env var exists but is empty, ?? doesn't use default)Quotes in Default Values:
Default values can be wrapped in single or double quotes:
[app]message = ${WELCOME_MSG||'Hello, World!'}api_url = ${API_URL||"https://api.example.com"}9. Child Sections
Child sections create a hierarchical relationship using dot notation.
Example:
# config.ini[server]host = 0.0.0.0[server.http]port = 8080timeout = 30s[server.https]port = 8443timeout = 60scert = /path/to/cert.pemcfg := ini.New(ini.Options{ ChildSectionDelimiter: ".", // Default is "."})cfg.Append("config.ini")// Access child sectionshttpSection := cfg.Section("server.http")httpPort := httpSection.MustInt("port", 80)httpsSection := cfg.Section("server.https")httpsPort := httpsSection.MustInt("port", 443)// Child sections inherit keys from parent sections// If server.http doesn't have host, it will look in serverhost := httpSection.String("host")// Returns "0.0.0.0" (inherited from parent section server)Getting Parent Section:
httpsSection := cfg.Section("server.https")parentSection, hasParent := httpsSection.Parent()if hasParent { fmt.Println(parentSection.Name()) // "server"}Options Configuration
type Options struct { // Loose indicates whether the parser ignores non-existent files Loose bool // Insensitive forces all section and key names to lowercase Insensitive bool // InsensitiveSections forces all section names to lowercase InsensitiveSections bool // InsensitiveKeys forces all key names to lowercase InsensitiveKeys bool // IgnoreContinuation ignores continuation lines IgnoreContinuation bool // IgnoreInlineComment ignores inline comments IgnoreInlineComment bool // AllowBooleanKeys allows boolean type keys (value is true) // Mainly for my.cnf type configuration files AllowBooleanKeys bool // AllowPythonMultilineValues allows Python-style multiline values AllowPythonMultilineValues bool // SpaceBeforeInlineComment requires space before comment symbol SpaceBeforeInlineComment bool // UnescapeValueDoubleQuotes unescapes quotes in double-quoted values UnescapeValueDoubleQuotes bool // UnescapeValueCommentSymbols unescapes comment symbols UnescapeValueCommentSymbols bool // KeyValueDelimiters key-value delimiters, default "=:" KeyValueDelimiters string // ChildSectionDelimiter child section delimiter, default "." ChildSectionDelimiter string // PreserveSurroundedQuote preserves quotes around values PreserveSurroundedQuote bool // ReaderBufferSize reader buffer size (bytes) ReaderBufferSize int // AllowNonUniqueSections allows sections with the same name AllowNonUniqueSections bool // AllowDuplicateShadowValues allows duplicate shadow values AllowDuplicateShadowValues bool // Mutex concurrency lock (customizable) Mutex Mutex // Transformer custom value transformer Transformer ValueTransformer}Common Configuration Examples
// Loose mode: ignore non-existent filescfg := ini.New(ini.Options{ Loose: true,})// Case-insensitivecfg := ini.New(ini.Options{ Insensitive: true,})// Allow boolean keys (MySQL configuration style)cfg := ini.New(ini.Options{ AllowBooleanKeys: true,})// Ignore inline commentscfg := ini.New(ini.Options{ IgnoreInlineComment: true,})// Custom delimiterscfg := ini.New(ini.Options{ KeyValueDelimiters: "=", // Only allow = as delimiter ChildSectionDelimiter: "::", // Use :: as child section delimiter})Use Cases
1. Application Configuration Management
// config.ini// [app]// name = MyApp// version = 1.0.0// debug = ${DEBUG||false}// port = ${PORT||8080}//// [database]// host = ${DB_HOST||localhost}// port = ${DB_PORT||3306}// name = ${DB_NAME||mydb}// user = ${DB_USER||root}// password = ${DB_PASSWORD}type Config struct { App AppConfig Database DatabaseConfig}type AppConfig struct { Name string Version string Debug bool Port int}type DatabaseConfig struct { Host string Port int Name string User string Password string}func LoadConfig() (*Config, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { return nil, err } config := &Config{ App: AppConfig{ Name: cfg.Section("app").String("name"), Version: cfg.Section("app").String("version"), Debug: cfg.Section("app").MustBool("debug", false), Port: cfg.Section("app").MustInt("port", 8080), }, Database: DatabaseConfig{ Host: cfg.Section("database").String("host"), Port: cfg.Section("database").MustInt("port", 3306), Name: cfg.Section("database").String("name"), User: cfg.Section("database").String("user"), Password: cfg.Section("database").String("password"), }, } return config, nil}2. Multi-Environment Configuration
// Load base configuration + environment-specific configurationcfg := ini.New(ini.Options{Loose: true})// Load in batcherr := cfg.Batch(func(m *ini.Manager) error { // Base configuration m.Append("config.ini") // Environment-specific configuration env := os.Getenv("APP_ENV") if env != "" { m.Append(fmt.Sprintf("config.%s.ini", env)) } // Local override configuration m.Append("config.local.ini") return nil})3. Service Configuration with Environment Variables
// config.ini// [redis]// host = ${REDIS_HOST||localhost}// port = ${REDIS_PORT||6379}// password = ${REDIS_PASSWORD}// database = ${REDIS_DB||0}// max_retries = 3// pool_size = ${REDIS_POOL_SIZE||10}func NewRedisClient() (*redis.Client, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { return nil, err } section := cfg.Section("redis") client := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", section.String("host"), section.MustInt("port", 6379)), Password: section.String("password"), DB: section.MustInt("database", 0), MaxRetries: section.MustInt("max_retries", 3), PoolSize: section.MustInt("pool_size", 10), }) return client, nil}4. Dynamic Configuration Reload
type ConfigManager struct { cfg *ini.Manager watchers []func(*ini.Manager) mu sync.RWMutex}func NewConfigManager(filename string) (*ConfigManager, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append(filename); err != nil { return nil, err } return &ConfigManager{ cfg: cfg, watchers: make([]func(*ini.Manager), 0), }, nil}func (cm *ConfigManager) Reload() error { cm.mu.Lock() defer cm.mu.Unlock() if err := cm.cfg.Reload(); err != nil { return err } // Notify all watchers for _, watcher := range cm.watchers { watcher(cm.cfg) } return nil}func (cm *ConfigManager) Watch(fn func(*ini.Manager)) { cm.mu.Lock() defer cm.mu.Unlock() cm.watchers = append(cm.watchers, fn)}func (cm *ConfigManager) GetInt(section, key string, defaultVal int) int { cm.mu.RLock() defer cm.mu.RUnlock() return cm.cfg.Section(section).MustInt(key, defaultVal)}5. Complex Configuration Validation
// config.ini// [server]// host = 0.0.0.0// port = 8080// workers = 4// timeout = 30s//// [limits]// max_connections = 1000// rate_limit = 100// request_size = 10485760func ValidateConfig(cfg *ini.Manager) error { // Validate port range port := cfg.Section("server").MustInt("port", 8080) if port < 1024 || port > 65535 { return fmt.Errorf("invalid port: %d (must be 1024-65535)", port) } // Validate worker count workers := cfg.Section("server").MustInt("workers", 4) if workers < 1 || workers > 256 { return fmt.Errorf("invalid workers: %d (must be 1-256)", workers) } // Validate timeout timeout, err := cfg.Section("server").Duration("timeout") if err != nil || timeout < time.Second || timeout > 5*time.Minute { return fmt.Errorf("invalid timeout: must be 1s-5m") } // Validate limits configuration maxConns := cfg.Section("limits").MustInt("max_connections", 100) if maxConns < 1 { return fmt.Errorf("max_connections must be positive") } return nil}API Reference
Manager Methods
| Method | Description |
|---|---|
New(opts Options) *Manager | Create new manager |
Append(source any, others ...any) error | Append data source |
Batch(fn func(m *Manager) error) error | Batch operations |
Reload() error | Reload |
Section(name string) *Section | Get section |
GetSection(name string) (*Section, error) | Get section (with error) |
HasSection(name string) bool | Check if section exists |
NewSection(name string) *Section | Create new section |
Section Methods
| Method | Description |
|---|---|
Name() string | Get section name |
Parent() (*Section, bool) | Get parent section |
Key(name string) *Key | Get key |
GetKey(name string) (*Key, error) | Get key (with error) |
HasKey(name string) bool | Check if key exists |
NewKey(name, value string) *Key | Create new key |
Keys() []*Key | Get all keys |
Value Reading Methods (Section/Key)
| Method | Description |
|---|---|
String(name string) string | String value |
MustString(name, defaultVal string) string | String with default |
Int/Int64/Uint/Uint64(name string) (T, error) | Integer value |
MustInt/MustInt64/...(name string, defaultVal T) T | Integer with default |
Float64(name string) (float64, error) | Float value |
MustFloat64(name, defaultVal float64) float64 | Float with default |
Bool(name string) (bool, error) | Boolean value |
MustBool(name string, defaultVal bool) bool | Boolean with default |
Duration(name string) (time.Duration, error) | Time duration |
Time/TimeFormat(name, format string) (time.Time, error) | Time value |
List Methods
| Method | Description |
|---|---|
Strings(name, delim string) []string | String list |
Ints/Int64s/Uints/Uint64s(name, delim string) []T | Integer list (invalid as 0) |
ValidInts/ValidInt64s/...(name, delim string) []T | Valid integer list (skip invalid) |
StrictInts/StrictInt64s/...(name, delim string) ([]T, error) | Strict integer list (error on invalid) |
Float64s/Bools/Times(name, delim string) []T | Other type lists |
Validation Methods
| Method | Description |
|---|---|
In(name, defaultVal string, candidates []string) string | Candidate check |
InInt/InFloat64/...(name, defaultVal, candidates) T | Typed candidate check |
RangeInt/RangeFloat64/...(name, defaultVal, min, max) T | Range check |
Validate(name, fn func(string) string) string | Custom validation |
Notes
1. Environment Variable Syntax Differences
Understand the difference between || and ??:
# || Empty fallback: use default when env var doesn't exist or is emptyvalue1 = ${VAR||default}# ?? Non-existent fallback: use default only when env var doesn't existvalue2 = ${VAR??default}// Set empty stringos.Setenv("VAR", "")cfg.Section("").String("value1") // "default" (|| checks for empty)cfg.Section("").String("value2") // "" (?? doesn't check for empty)2. Default Section
Key-value pairs without a section name belong to the default section (empty string name):
# Default sectionroot = /var/wwwdebug = true[app]name = MyApp// Access default sectionroot := cfg.Section("").String("root")debug := cfg.Section("").MustBool("debug", false)3. Key Name Case Sensitivity
Depending on Options settings, key names may be converted to lowercase:
// Case-sensitive (default)cfg := ini.New(ini.Options{})cfg.Section("app").String("UserName") // Must match exactly// Case-insensitivecfg := ini.New(ini.Options{Insensitive: true})cfg.Section("app").String("username") // Can match UserName, USERNAME, etc.cfg.Section("app").String("USERNAME") // Also works4. Multi-Source Merging
Later loaded data sources will override earlier ones with the same key name:
cfg := ini.New(ini.Options{})cfg.Append("base.ini") // port = 8080cfg.Append("override.ini") // port = 9000port := cfg.Section("app").MustInt("port")// Returns 9000 (override.ini overrode base.ini)5. Type Conversion Failures
Using Must* series functions, conversion failures will use the default instead of erroring:
// config.ini: port = invalidport, err := cfg.Section("app").Int("port")// err != nil, port = 0port := cfg.Section("app").MustInt("port", 8080)// port = 8080 (conversion failed, use default)6. Variable Reference Cycles
Avoid circular references, or they will reach the maximum depth (99 levels):
# Bad example: circular reference[bad]a = %(b)sb = %(a)s7. Thread Safety
Manager internally uses mutex locks to protect concurrent access. You can customize the Mutex:
cfg := ini.New(ini.Options{ Mutex: &sync.RWMutex{}, // Use read-write lock})8. Child Section Inheritance
Child sections inherit keys from parent sections, but do not recursively inherit:
[a]key1 = value1[a.b]key2 = value2[a.b.c]key3 = value3// a.b.c can access keys from a.b, but cannot directly access keys from acfg.Section("a.b.c").String("key2") // OK (inherited from a.b)cfg.Section("a.b.c").String("key1") // Not found (doesn't recursively inherit)Best Practices
1. Use Environment Variables for Sensitive Configuration
# config.ini - commit to version control[database]host = ${DB_HOST||localhost}port = ${DB_PORT||3306}name = ${DB_NAME||mydb}user = ${DB_USER||root}# Password from environment variable, no defaultpassword = ${DB_PASSWORD}[api]# API keys must be provided from environment variableskey = ${API_KEY}secret = ${API_SECRET}2. Layered Configuration Files
// Base -> Environment -> Localcfg := ini.New(ini.Options{Loose: true})cfg.Batch(func(m *ini.Manager) error { m.Append("config.ini") // Base configuration m.Append("config.production.ini") // Production environment m.Append("config.local.ini") // Local override (not committed) return nil})3. Configuration Validation
func LoadAndValidateConfig() (*ini.Manager, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { return nil, err } // Validate required configuration required := map[string][]string{ "database": {"host", "port", "name", "user", "password"}, "redis": {"host", "port"}, } for section, keys := range required { for _, key := range keys { if !cfg.Section(section).HasKey(key) { return nil, fmt.Errorf("missing required config: %s.%s", section, key) } } } return cfg, nil}4. Use Structured Configuration
type Config struct { Server ServerConfig Database DatabaseConfig Redis RedisConfig}func (c *Config) Load(cfg *ini.Manager) error { c.Server.Load(cfg.Section("server")) c.Database.Load(cfg.Section("database")) c.Redis.Load(cfg.Section("redis")) return c.Validate()}func (c *Config) Validate() error { // Centralized validation logic return nil}5. Provide Configuration Template
# config.example.ini - Configuration template# Copy to config.ini and fill in actual values[app]name = MyAppdebug = ${DEBUG||false}port = ${PORT||8080}[database]host = ${DB_HOST||localhost}port = ${DB_PORT||3306}name = ${DB_NAME||mydb}user = ${DB_USER||root}password = ${DB_PASSWORD} # Required: Database password