moonini

    Lossless INI concrete syntax and surgical source editing

    ini
    lossless
    source-editing
    diagnostics
    Download zip
    Author
    Version
    0.1.1
    License
    MIT
    Last updated
    4 hours ago
    Downloads
    2

    #MoonINI

    MoonINI 是一个 INI 专用、格式保留、可诊断、可编辑 的 MoonBit 库。它面向传统桌面应用、游戏、CLI 和老系统迁移工具,不试图成为 TOML/YAML 或 MoonBit 工程配置的统一框架。

    • GitHub:https://github.com/shangwuxi/moonini
    • MoonCakes:https://mooncakes.io/docs/shangwuxi/moonini
    • 包名:shangwuxi/moonini
    • 版本:0.1.1

    #安装

    moon add shangwuxi/moonini

    #核心契约

    render(parse(text)) == text

    ParseLimits 允许的范围内,解析再渲染会逐字保留原始文本:包括 BOM、CRLF/LF/CR 混合换行、注释、空白、重复条目和无法完全理解的行。修改只替换目标 source span 或插入/删除必要行;操作完成后会重新解析并验证,不返回半成功状态。

    #三个示例

    可运行入口:

    moon run cmd/demo

    #1. 游戏设置:只改一个值,保留 CRLF 和注释

    ; player preferences [video] quality = 'medium' ; retain vsync=on

    quality 改成 high 后,注释、单引号和 CRLF 都还在:

    ; player preferences [video] quality = 'high' ; retain vsync=on

    #2. CLI 配置:重复键诊断、修复,再做 schema 校验

    输入有两个 port。MoonINI 报告稳定位置(INI009),删除第二次出现后校验 port 范围和 enabled 布尔值。

    #3. 老配置迁移:BOM、多行值、事务编辑

    带 BOM 的 extended 方言文件里,三引号多行 script 被整体替换,同时插入 enabled、删除 old。失败则整批回退,不留下半成品。

    对应代码在 cmd/demo/main.mbt

    #API 入口

    let parsed = @moonini.parse("[server]\nport=80\ndebug=true\n")
    match parsed.set("server", "port", "8080") {
    Err(d) => println(d.format())
    Ok(updated) => {
    match updated.remove("server", "debug") {
    Err(d) => println(d.format())
    Ok(removed) => {
    let findings = removed.validate_schema([
    @moonini.rule("server", "port", @moonini.Integer, required=true),
    ])
    for d in findings { println(d.format()) }
    println(removed.render())
    }
    }
    }
    }

    示例假定调用包将本地库导入为 @moonini;可直接运行的包配置见 cmd/demo/moon.pkg。编辑返回 Result[Document, Diagnostic],schema 返回诊断数组,不会修改文档。调用者应先检查 has_errors();occurrence 索引从 0 开始。Span 使用从 0 开始的 UTF-16 半开区间,行列从 1 开始,不是 UTF-8 字节偏移。

    同时提供 parse_with_diagnosticsget_intget_boolset_atremove_atapply。诊断格式为 line:column: severity CODE: message,不会回显原始值。

    #能力

    • section、全局 key-value、注释和空白的有序具体语法树
    • UTF-16 source offset、行号、列号和稳定诊断码
    • 引号值、转义、行内注释和扩展方言三引号多行值
    • 重复 key 的 RejectFirstWinsLastWins 策略,并标注首个出现位置
    • getsectionskeys 查询
    • setinsertremove 和原子 apply 编辑
    • 严格的 Int32/Bool 转换
    • 受限的逐 key schema 校验(required、类型、范围、choices、unknown)
    • 可配置 dialect 与 ParseLimits

    #验证

    moon fmt --check moon check --target wasm-gc --deny-warn moon test --target wasm-gc --deny-warn moon run cmd/demo --target wasm-gc

    本地已验证 wasm-gc、wasm、js 的 check/build/test(53 个测试)。本机没有 C 编译器,因此 native 仅完成 moon check

    #边界与差异

    MoonINI 不实现 TOML、YAML、moon.mod/moon.pkg、分层合并、通用 diff 应用或跨格式 schema。MoonCakes 和 GitHub 上已有 INI 包覆盖规范化查询/重写;本项目的独立贡献是具体语法级 source span、未修改区域逐字保留、窄范围编辑,以及编辑后重新 parse/verify。检索和对比证据见 docs/competition/duplicate-check.md

    #项目状态

    GitHub 与 MoonCakes 同步发布。许可证为 MIT;说明见 AI_USAGE.mdCONTRIBUTING.mdSECURITY.md

    Diagnostic

    pub(all) struct Diagnostic {
    code : String
    severity : Severity
    message : String
    span : Span
    related : Span?
    } derive(Eq,
    Debug
    )

    Diagnostic::format

    fn Diagnostic::format(self : Diagnostic) -> String

    Deterministic plain-text diagnostic; does not echo potentially secret values.

    Dialect

    pub(all) struct Dialect {
    allow_colon : Bool
    hash_comments : Bool
    inline_comments : Bool
    case_sensitive : Bool
    multiline : Bool
    duplicates : DuplicatePolicy
    } derive(Eq,
    Debug
    )

    Document

    pub struct Document {
    // private fields
    }

    Owned immutable source snapshot; edits return a fresh snapshot.

    Document::apply

    fn Document::apply(self : Document, edits : Array[Edit]) -> Result[Document, EditFailure]

    Sequential edits against freshly parsed snapshots. Error returns no partial doc.

    Document::diagnostics

    fn Document::diagnostics(self : Document) -> Array[Diagnostic]

    Document::entries

    fn Document::entries(self : Document, section : String, key : String) -> Array[Node]

    Document::get

    fn Document::get(self : Document, section : String, key : String) -> String?

    Reject policy returns None for ambiguous duplicates. Inspect diagnostics for cause.

    Document::get_bool

    fn Document::get_bool(self : Document, section : String, key : String) -> Result[Bool, Diagnostic]

    Document::get_int

    fn Document::get_int(self : Document, section : String, key : String) -> Result[Int, Diagnostic]

    Document::has_errors

    fn Document::has_errors(self : Document) -> Bool

    Document::insert

    fn Document::insert(self : Document, section : String, key : String, value : String) -> Result[Document, Diagnostic]

    Append a new key to the last matching section block; create a section if absent. Existing text remains an unchanged prefix/suffix; new lines use first observed EOL.

    Document::keys

    fn Document::keys(self : Document, section : String) -> Array[String]

    Document::nodes

    fn Document::nodes(self : Document) -> Array[Node]

    Document::remove

    fn Document::remove(self : Document, section : String, key : String) -> Result[Document, Diagnostic]

    Remove every occurrence and its physical line(s), including inline comments. Standalone comments, empty sections and all other text remain untouched.

    Document::remove_at

    fn Document::remove_at(self : Document, section : String, key : String, occurrence : Int) -> Result[Document, Diagnostic]

    Document::render

    fn Document::render(self : Document) -> String

    Document::sections

    fn Document::sections(self : Document) -> Array[String]

    Unique explicit section names in source order, including empty sections.

    Document::set

    fn Document::set(self : Document, section : String, key : String, value : String) -> Result[Document, Diagnostic]

    Set the effective existing occurrence; Reject refuses ambiguous duplicates.

    Document::set_at

    fn Document::set_at(self : Document, section : String, key : String, occurrence : Int, value : String) -> Result[Document, Diagnostic]

    Replace one duplicate occurrence (zero-based) without touching any surroundings. Syntax errors block edits; duplicate diagnostics alone do not.

    Document::validate_schema

    fn Document::validate_schema(self : Document, rules : Array[Rule], allow_unknown? : Bool) -> Array[Diagnostic]

    Validate effective values. Syntax diagnostics remain on Document separately. Output order: rule order, then unknown keys in physical order.

    DuplicatePolicy

    pub(all) enum DuplicatePolicy {
    FirstWins
    LastWins
    Reject
    } derive(Eq,
    Debug
    )

    Edit

    pub(all) enum Edit {
    Set(String, String, String)
    SetAt(String, String, Int, String)
    Insert(String, String, String)
    Remove(String, String)
    RemoveAt(String, String, Int)
    } derive(Eq,
    Debug
    )

    EditFailure

    pub(all) struct EditFailure {
    index : Int
    diagnostic : Diagnostic
    } derive(Eq,
    Debug
    )

    Line

    pub(all) struct Line {
    start : Int
    content_end : Int
    end : Int
    number : Int
    } derive(Eq,
    Debug
    )

    Physical lines include their exact terminator, excluding a leading BOM.

    Node

    pub(all) struct Node {
    kind : NodeKind
    span : Span
    section : String
    key : String
    value : String
    key_span : Span
    value_span : Span
    } derive(Eq,
    Debug
    )

    Snapshot value object. Changing returned arrays cannot mutate a Document.

    NodeKind

    pub(all) enum NodeKind {
    Blank
    Comment
    Section
    Entry
    Invalid
    } derive(Eq,
    Debug
    )

    ParseLimits

    pub(all) struct ParseLimits {
    max_source_units : Int
    max_lines : Int
    } derive(Eq,
    Debug
    )

    Rule

    pub(all) struct Rule {
    section : String
    key : String
    required : Bool
    kind : ValueType
    minimum : Int?
    maximum : Int?
    choices : Array[String]
    } derive(Eq,
    Debug
    )

    Per-key INI constraint, not JSON Schema/OpenAPI or cross-format configuration.

    Severity

    pub(all) enum Severity {
    Error
    Warning
    } derive(Eq,
    Debug
    )

    Span

    pub(all) struct Span {
    start : Int
    end : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    Half-open UTF-16 offsets; line and column are 1-based UTF-16 units, not bytes.

    ValueType

    pub(all) enum ValueType {
    Text
    Integer
    Boolean
    } derive(Eq,
    Debug
    )

    default_limits

    fn default_limits() -> ParseLimits

    extended

    fn extended() -> Dialect

    Explicit convenience preset. Not Python configparser interpolation semantics.

    legacy

    fn legacy() -> Dialect

    A documented legacy-friendly subset, NOT full Win32 API emulation.

    parse

    fn parse(source : String, dialect? : Dialect, limits? : ParseLimits) -> Document

    parse_with_diagnostics

    fn parse_with_diagnostics(source : String, dialect? : Dialect, limits? : ParseLimits) -> (Document, Array[Diagnostic])

    rule

    fn rule(section : String, key : String, kind : ValueType, required? : Bool, minimum? : Int?, maximum? : Int?, choices? : Array[String]) -> Rule

    scan_lines

    fn scan_lines(source : String) -> Array[Line]

    standard

    fn standard() -> Dialect