Skip to content

Latest commit

 

History

History
668 lines (485 loc) · 11.3 KB

File metadata and controls

668 lines (485 loc) · 11.3 KB

INI Parser - Parsing Rules

This document describes how the @notfounnd/ini-parser interprets INI files.


Table of Contents

  1. Processing Order
  2. Basic Structure
  3. Comments
  4. Multi-line Values
  5. Value Splitting
  6. Output Formats
  7. Special Cases
  8. Supported File Extensions
  9. Known Limitations
  10. Complete Examples

Processing Order

The parser processes each line of the INI file sequentially, applying the following logic:

  1. Trim whitespace from the line
  2. Check if empty or comment → skip the line
  3. Check if section ([...]) → initialize new section
  4. Check if global key (no current section) → process as global key
  5. Check if indented line → add as value to previous key
  6. Check if key-value pair (key=value) → process normally
  7. Check if non-indented value (after empty key) → add as value

Basic Structure

Sections

Sections group related configuration keys together.

Syntax:

  • Defined by [section_name]
  • Must be on their own line
  • Section name is extracted by removing [ and ] and trimming whitespace
  • Sections can be empty (no keys inside)

Example:

[database]
host=localhost

[empty_section]

Output:

{
  "database": {
    "host": ["localhost"]
  },
  "empty_section": {}
}

Global Keys

Keys defined before any section are considered global.

Behavior:

  • Global keys don't belong to any section
  • Processed identically to keys within sections
  • Appear at the root level of the output

Example:

app_name=MyApp
version=1.0.0

[database]
host=localhost

Output:

{
  "app_name": ["MyApp"],
  "version": ["1.0.0"],
  "database": {
    "host": ["localhost"]
  }
}

Key-Value Pairs

Syntax:

  • Format: key=value
  • Spaces around = are allowed and will be removed (trimmed)
  • Keys without = are ignored (unless they are indented values)

Example:

host=localhost
port = 5432
name  =  test

Output:

{
  "host": ["localhost"],
  "port": ["5432"],
  "name": ["test"]
}

Comments

Comment Characters

Two characters indicate comments:

  • # (hash)
  • ; (semicolon)

Line Comments

Lines starting with # or ; are completely ignored.

Example:

# This is a comment
; This is also a comment
[section]
key=value

Output:

{
  "section": {
    "key": ["value"]
  }
}

Inline Comments

Everything after # or ; until the end of the line is considered a comment and removed.

Example:

host=localhost # this is the local server
port=5432 ; default PostgreSQL port

Output:

{
  "host": ["localhost"],
  "port": ["5432"]
}

Comments in Values

IMPORTANT: If # or ; appears in a value, everything after it is removed, even if it's part of the original value.

Example:

connection_string=server=localhost;database=test

Output:

{
  "connection_string": ["server=localhost"]
}

⚠️ Warning: This means SQL connection strings or other values using ; as a separator will be truncated. This is standard INI specification behavior.


Multi-line Values

Indented Values

Lines starting with spaces or tabs are considered indented values and belong to the previous key.

Example:

[section]
key=
    value1
    value2
    value3

Output:

{
  "section": {
    "key": ["value1", "value2", "value3"]
  }
}

First Value on Same Line

The first value can be on the same line as the key, with additional indented values following.

Example:

servers=prod1
    prod2
    prod3

Output:

{
  "servers": ["prod1", "prod2", "prod3"]
}

Non-indented Values After Empty Key

If a key has no initial value (just key=), subsequent non-indented lines are treated as values until a new key or section is encountered.

Example:

key=
value1
value2

Output:

{
  "key": ["value1", "value2"]
}

Value Splitting

Values with Spaces

Values containing whitespace (spaces, tabs, etc.) are automatically split into multiple values during parsing.

Example:

tags=production stable v1.0

Output:

{
  "tags": ["production", "stable", "v1.0"]
}

Values with Spaces and Equals Signs

IMPORTANT: Values containing spaces are split regardless of whether they contain = or not.

This ensures consistency, as the INI format doesn't support multi-word strings.

Example:

params=timeout=30 retry=3

Output:

{
  "params": ["timeout=30", "retry=3"]
}

Equivalent to:

params=
    timeout=30
    retry=3

⚠️ Notes:

  • Remember that ; in values will be treated as a comment (see Comments in Values)
  • .properties files with multi-word values will be split (use a properties-specific library if you need to preserve spaces)
  • Splitting occurs during parsing, not as a separate post-processing step

Indented Values with Equals Signs

Indented values containing = are treated as normal values, not as new key declarations.

Example (correct - no spaces around =):

[pytest]
addopts=
    --cov-config=.coveragerc
    --cov-context=test

Output:

{
  "pytest": {
    "addopts": ["--cov-config=.coveragerc", "--cov-context=test"]
  }
}

Malformed Indented Values

⚠️ IMPORTANT: If an indented line contains = with spaces around it, it will be automatically split during parsing (see Values with Spaces).

Example (malformed):

key = value
    key_indented = value_indented

Output:

{
  "key": ["value", "key_indented", "=", "value_indented"]
}

The value "key_indented = value_indented" contains spaces and is split into multiple array elements.

How to avoid this:

  1. Don't indent key declarations: place them at the beginning of the line
  2. Remove spaces around = in indented values: use key_indented=value_indented

Example (corrected):

key = value
    key_indented=value_indented

Output:

{
  "key": ["value", "key_indented=value_indented"]
}

Output Formats

Simplified Format (Default)

By default (meta: false), the parser returns a simplified format:

Input:

global_key=value

[section_name]
key=value1
    value2

Output:

{
  "global_key": ["value"],
  "section_name": {
    "key": ["value1", "value2"]
  }
}

Format with Metadata

With meta: true, the parser returns a format with type information:

Input:

global_key=value

[section_name]
key=value1
    value2

Output:

{
  "global_key": {
    "type": "configuration",
    "content": ["value"]
  },
  "section_name": {
    "type": "section",
    "content": {
      "key": {
        "type": "configuration",
        "content": ["value1", "value2"]
      }
    }
  }
}

Special Cases

Empty File

Returns empty object {}.

Invalid Input

  • null, undefined, or non-string: returns {}
  • Empty string: returns {}

Empty Sections

Sections without keys result in an empty object for that section.

Example:

[empty_section]

[normal_section]
key=value

Output:

{
  "empty_section": {},
  "normal_section": {
    "key": ["value"]
  }
}

Empty Lines

Empty lines or lines with only whitespace are ignored.

Comment-only Files

Files containing only comments return {}.

Empty Values

Keys with no value and no continuation values result in an empty array.

Example:

[section]
empty_key=
key_with_value=test

Output:

{
  "section": {
    "empty_key": [],
    "key_with_value": ["test"]
  }
}

Supported File Extensions

The parser works with any file extension that follows the INI format:

  • .ini - Traditional INI files
  • .config - Configuration files
  • .properties - Java-style properties files
  • Any other extension using INI format

Known Limitations

  1. Values with semicolons: Any value containing ; will be truncated (treated as inline comment)
  2. No quote support: Values with quotes have no special handling
  3. No escape sequences: Escape characters (\) are not processed
  4. No nested sections: [section.subsection] is treated as a unique name, not nested structure
  5. Order not preserved: Key order is not guaranteed (JavaScript object limitation)
  6. File paths with spaces: Any path containing spaces will be split into multiple values. Use paths without spaces or store paths as multi-value arrays

Complete Examples

Example 1: Full Configuration File

Input:

# Global configuration
app_name=MyApp
version=1.0.0

[database]
host=localhost
port=5432 ; default PostgreSQL port
users=
    admin
    readonly
    backup

[features]
enabled_modules=auth api logging

Output:

{
  "app_name": ["MyApp"],
  "version": ["1.0.0"],
  "database": {
    "host": ["localhost"],
    "port": ["5432"],
    "users": ["admin", "readonly", "backup"]
  },
  "features": {
    "enabled_modules": ["auth", "api", "logging"]
  }
}

Example 2: Pytest-style Configuration

Input:

[pytest]
addopts=
    -rA
    --cov=package
    --cov-config=.coveragerc
testpaths=
    tests

Output:

{
  "pytest": {
    "addopts": ["-rA", "--cov=package", "--cov-config=.coveragerc"],
    "testpaths": ["tests"]
  }
}

Example 3: Mixed Global and Sectioned Keys

Input:

; Application metadata
title=Configuration Parser
author=NotFound

[server]
host=0.0.0.0
port=8080
allowed_origins=http://localhost:3000 http://localhost:8080

[logging]
level=info
format=json
outputs=
    console
    file

Output:

{
  "title": ["Configuration Parser"],
  "author": ["NotFound"],
  "server": {
    "host": ["0.0.0.0"],
    "port": ["8080"],
    "allowed_origins": ["http://localhost:3000", "http://localhost:8080"]
  },
  "logging": {
    "level": ["info"],
    "format": ["json"],
    "outputs": ["console", "file"]
  }
}

See Also

  • API Reference - Complete API documentation for using the parser programmatically.
  • CLI Reference - Complete guide for using the command-line interface.

Made with ❤️ by Júnior Sbrissa | Errør 404 | NotFounnd