Slim

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/ini

Quick 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 = 0

Core 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) *Manager

Description:

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) error

Description:

Appends one or more data sources and automatically reloads. Supports multiple data source types.

Supported Data Source Types:

  • string: File path
  • []byte: Byte slice
  • io.Reader: Reader
  • io.ReadCloser: Closable reader
  • DataSource: Custom data source interface
  • func() (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) error

Description:

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() error

Description:

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) *Section

Description:

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) bool

Description:

Checks if the specified section exists.

Example:

if cfg.HasSection("database") {    // Database configuration exists}

NewSection - Create New Section

func (m *Manager) NewSection(name string) *Section

Description:

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) *Key

Description:

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) bool

Example:

section := cfg.Section("database")if section.HasKey("password") {    // Password configuration exists}

NewKey - Create New Key

func (s *Section) NewKey(name, value string) *Key

Description:

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() []*Key

Description:

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() string

Example:

// 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) string

Example:

// 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) uint64

Example:

// 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) float64

Example:

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) bool

Supported 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.Duration

Example:

// 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.Time

Example:

// 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) []string

Description:

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) []uint64

Description:

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) []int64

Description:

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) float64

Description:

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 8080

Range - 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.Time

Description:

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) string

Description:

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)s
cfg := 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/data
dataDir := 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.pem
cfg := 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

MethodDescription
New(opts Options) *ManagerCreate new manager
Append(source any, others ...any) errorAppend data source
Batch(fn func(m *Manager) error) errorBatch operations
Reload() errorReload
Section(name string) *SectionGet section
GetSection(name string) (*Section, error)Get section (with error)
HasSection(name string) boolCheck if section exists
NewSection(name string) *SectionCreate new section

Section Methods

MethodDescription
Name() stringGet section name
Parent() (*Section, bool)Get parent section
Key(name string) *KeyGet key
GetKey(name string) (*Key, error)Get key (with error)
HasKey(name string) boolCheck if key exists
NewKey(name, value string) *KeyCreate new key
Keys() []*KeyGet all keys

Value Reading Methods (Section/Key)

MethodDescription
String(name string) stringString value
MustString(name, defaultVal string) stringString with default
Int/Int64/Uint/Uint64(name string) (T, error)Integer value
MustInt/MustInt64/...(name string, defaultVal T) TInteger with default
Float64(name string) (float64, error)Float value
MustFloat64(name, defaultVal float64) float64Float with default
Bool(name string) (bool, error)Boolean value
MustBool(name string, defaultVal bool) boolBoolean with default
Duration(name string) (time.Duration, error)Time duration
Time/TimeFormat(name, format string) (time.Time, error)Time value

List Methods

MethodDescription
Strings(name, delim string) []stringString list
Ints/Int64s/Uints/Uint64s(name, delim string) []TInteger list (invalid as 0)
ValidInts/ValidInt64s/...(name, delim string) []TValid integer list (skip invalid)
StrictInts/StrictInt64s/...(name, delim string) ([]T, error)Strict integer list (error on invalid)
Float64s/Bools/Times(name, delim string) []TOther type lists

Validation Methods

MethodDescription
In(name, defaultVal string, candidates []string) stringCandidate check
InInt/InFloat64/...(name, defaultVal, candidates) TTyped candidate check
RangeInt/RangeFloat64/...(name, defaultVal, min, max) TRange check
Validate(name, fn func(string) string) stringCustom 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 works

4. 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)s

7. 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

Navigate Select esc Close search