dotenv

    Dotenv for MoonBit - loads environment variables from .env files

    dotenv
    env
    config
    environment
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    68

    #spectacleCase/dotenv

    MoonBit 版 Dotenv —— 从 .env 文件中加载环境变量。

    移植自 motdotla/dotenv (npm)。

    #安装

    moon add spectacleCase/dotenv

    #使用方法

    #parse

    将 .env 格式的字符串解析为 Map[String, String]。

    ///|
    let result = @dotenv.parse("DB_HOST=localhost\nDB_PORT=5432")

    test "parse basic key-value" { let result = @dotenv.parse("DB_HOST=localhost\nDB_PORT=5432") assert_eq(result["DB_HOST"], "localhost") assert_eq(result["DB_PORT"], "5432") }

    #支持的格式

    引号值 —— 单引号(字面量)、双引号(支持转义序列)、反引号(字面量):

    test "parse quoted values" { let result = @dotenv.parse("SINGLE='hello'\nDOUBLE="world"") assert_eq(result["SINGLE"], "hello") assert_eq(result["DOUBLE"], "world") }

    换行转义 —— 双引号值中的 \n 和 \r:

    test "parse newline escapes" { let result = @dotenv.parse("KEY="line1\nline2"") assert_eq(result["KEY"], "line1\nline2") }

    多行值 —— 引号内的实际换行:

    test "parse multiline values" { let result = @dotenv.parse("KEY="line1\nline2\nline3"") assert_eq(result["KEY"], "line1\nline2\nline3") }

    export 前缀 —— 静默忽略:

    test "parse export prefix" { let result = @dotenv.parse("export APP_ENV=production") assert_eq(result["APP_ENV"], "production") }

    行内注释 —— # 开始注释:

    test "parse inline comments" { let result = @dotenv.parse("KEY=value # this is a comment") assert_eq(result["KEY"], "value") }

    空值:

    test "parse empty values" { let result = @dotenv.parse("EMPTY=\nQUOTED_EMPTY=""") assert_eq(result["EMPTY"], "") assert_eq(result["QUOTED_EMPTY"], "") }

    重复键 —— 后者覆盖前者:

    test "parse duplicate keys" { let result = @dotenv.parse("KEY=first\nKEY=second") assert_eq(result["KEY"], "second") }

    #expand

    展开已解析值中的 $VAR 和 ${VAR} 变量引用。

    ///|
    let parsed = @dotenv.parse("BASE=hello\nREF=${BASE}_world")

    ///|
    let result = @dotenv.expand(parsed)

    test "expand variables" { let parsed = @dotenv.parse("BASE=hello\nREF=${BASE}_world") let result = @dotenv.expand(parsed) assert_eq(result["REF"], "hello_world") }

    可以传入外部 env Map 来补充 .env 文件中未定义的变量:

    test "expand with external env" { let parsed = @dotenv.parse("REF=$EXTERNAL") let env = Map([("EXTERNAL", "from_env")], capacity=1) let result = @dotenv.expand(parsed, env=env) assert_eq(result["REF"], "from_env") }

    已解析 Map 中的变量优先级高于外部 env:

    test "expand precedence" { let parsed = @dotenv.parse("KEY=parsed\nREF=$KEY") let env = Map([("KEY", "env")], capacity=1) let result = @dotenv.expand(parsed, env=env) assert_eq(result["REF"], "parsed") }

    未定义的变量保持原样:

    test "expand undefined variable" { let parsed = @dotenv.parse("REF=UNDEFINED") }

    #stringify

    将 Map[String, String] 序列化为 .env 格式字符串。需要转义的值会自动双引号包裹。

    test "stringify roundtrip" { let parsed = @dotenv.parse("HOST=localhost\nPORT=5432") let output = @dotenv.stringify(parsed) let reparsed = @dotenv.parse(output) assert_eq(reparsed["HOST"], "localhost") assert_eq(reparsed["PORT"], "5432") }

    #diff

    对比两个 Map,返回新增、删除、变更的键。

    test "diff detects changes" { let base = @dotenv.parse("A=1\nB=2") let other = @dotenv.parse("A=1\nC=3") let d = @dotenv.diff(base, other) assert_eq(d.removed.length(), 1) assert_eq(d.added.length(), 1) }

    #try_parse

    类似 parse,但遇到未闭合引号时返回 Err(ParseError) 而非静默忽略。

    test "try_parse error" { let result = @dotenv.try_parse("KEY='unclosed") assert_true(result is Err(_)) }

    #defaults

    为缺失的键设置默认值,不影响已存在的键。

    test "defaults fills missing" { let env = @dotenv.parse("HOST=localhost") let defs = Map([("PORT", "5432"), ("HOST", "127.0.0.1")], capacity=2) let result = @dotenv.defaults(env, defs) assert_eq(result["HOST"], "localhost") assert_eq(result["PORT"], "5432") }

    #validate

    校验必需的 key 是否存在,返回缺失的 key 列表。

    test "validate finds missing keys" { let env = @dotenv.parse("HOST=localhost") let missing = @dotenv.validate(env, ["HOST", "PORT", "DB"]) assert_eq(missing.length(), 2) }

    #$$ 转义

    $$ 在 expand 中表示字面量 $,不会触发变量展开:

    test "dollar dollar escape" { let parsed = @dotenv.parse("PRICE=$$100") let result = @dotenv.expand(parsed) assert_eq(result["PRICE"], "$100") }

    #API 参考

    函数签名说明
    parse(String) -> Map[String, String]将 .env 字符串解析为键值对 Map(自动剥离 UTF-8 BOM)
    try_parse(String) -> Result[Map[String, String], ParseError]解析,遇到错误返回 Err
    expand(Map[String, String], env? : Map[String, String]) -> Map[String, String]展开 $VAR / ${VAR},$$ 表示字面量 $
    stringify(Map[String, String]) -> String将 Map 序列化为 .env 格式字符串
    diff(Map[String, String], Map[String, String]) -> DiffResult对比两个 Map,返回 added/removed/changed
    defaults(Map[String, String], Map[String, String]) -> Map[String, String]为缺失的键设置默认值
    validate(Map[String, String], Array[String]) -> Array[String]校验必需 key,返回缺失列表
    config(String, env? : Map[String, String], force? : Bool) -> Map[String, String]解析并合并环境变量,可选 force 覆盖
    merge(Map[String, String], Map[String, String], force? : Bool) -> Map[String, String]合并两个 Map
    load_multi(Array[String]) -> Map[String, String]解析多个 .env 字符串并合并
    config_multi(Array[String], env? : Map[String, String], force? : Bool) -> Map[String, String]多文件解析合并,可与外部 env 合并
    populate(Map[String, String], Map[String, String], force? : Bool) -> Int将解析结果写入目标 Map,返回写入数量

    #许可证

    Apache-2.0

    #spectacleCase/dotenv

    MoonBit 版 Dotenv —— 从 .env 文件中加载环境变量。

    移植自 motdotla/dotenv (npm)。

    #安装

    moon add spectacleCase/dotenv

    #使用方法

    #parse

    将 .env 格式的字符串解析为 Map[String, String]。

    ///|
    let result = @dotenv.parse("DB_HOST=localhost\nDB_PORT=5432")

    test "parse basic key-value" { let result = @dotenv.parse("DB_HOST=localhost\nDB_PORT=5432") assert_eq(result["DB_HOST"], "localhost") assert_eq(result["DB_PORT"], "5432") }

    #支持的格式

    引号值 —— 单引号(字面量)、双引号(支持转义序列)、反引号(字面量):

    test "parse quoted values" { let result = @dotenv.parse("SINGLE='hello'\nDOUBLE="world"") assert_eq(result["SINGLE"], "hello") assert_eq(result["DOUBLE"], "world") }

    换行转义 —— 双引号值中的 \n 和 \r:

    test "parse newline escapes" { let result = @dotenv.parse("KEY="line1\nline2"") assert_eq(result["KEY"], "line1\nline2") }

    多行值 —— 引号内的实际换行:

    test "parse multiline values" { let result = @dotenv.parse("KEY="line1\nline2\nline3"") assert_eq(result["KEY"], "line1\nline2\nline3") }

    export 前缀 —— 静默忽略:

    test "parse export prefix" { let result = @dotenv.parse("export APP_ENV=production") assert_eq(result["APP_ENV"], "production") }

    行内注释 —— # 开始注释:

    test "parse inline comments" { let result = @dotenv.parse("KEY=value # this is a comment") assert_eq(result["KEY"], "value") }

    空值:

    test "parse empty values" { let result = @dotenv.parse("EMPTY=\nQUOTED_EMPTY=""") assert_eq(result["EMPTY"], "") assert_eq(result["QUOTED_EMPTY"], "") }

    重复键 —— 后者覆盖前者:

    test "parse duplicate keys" { let result = @dotenv.parse("KEY=first\nKEY=second") assert_eq(result["KEY"], "second") }

    #expand

    展开已解析值中的 $VAR 和 ${VAR} 变量引用。

    ///|
    let parsed = @dotenv.parse("BASE=hello\nREF=${BASE}_world")

    ///|
    let result = @dotenv.expand(parsed)

    test "expand variables" { let parsed = @dotenv.parse("BASE=hello\nREF=${BASE}_world") let result = @dotenv.expand(parsed) assert_eq(result["REF"], "hello_world") }

    可以传入外部 env Map 来补充 .env 文件中未定义的变量:

    test "expand with external env" { let parsed = @dotenv.parse("REF=$EXTERNAL") let env = Map([("EXTERNAL", "from_env")], capacity=1) let result = @dotenv.expand(parsed, env=env) assert_eq(result["REF"], "from_env") }

    已解析 Map 中的变量优先级高于外部 env:

    test "expand precedence" { let parsed = @dotenv.parse("KEY=parsed\nREF=$KEY") let env = Map([("KEY", "env")], capacity=1) let result = @dotenv.expand(parsed, env=env) assert_eq(result["REF"], "parsed") }

    未定义的变量保持原样:

    test "expand undefined variable" { let parsed = @dotenv.parse("REF=UNDEFINED") }

    #stringify

    将 Map[String, String] 序列化为 .env 格式字符串。需要转义的值会自动双引号包裹。

    test "stringify roundtrip" { let parsed = @dotenv.parse("HOST=localhost\nPORT=5432") let output = @dotenv.stringify(parsed) let reparsed = @dotenv.parse(output) assert_eq(reparsed["HOST"], "localhost") assert_eq(reparsed["PORT"], "5432") }

    #diff

    对比两个 Map,返回新增、删除、变更的键。

    test "diff detects changes" { let base = @dotenv.parse("A=1\nB=2") let other = @dotenv.parse("A=1\nC=3") let d = @dotenv.diff(base, other) assert_eq(d.removed.length(), 1) assert_eq(d.added.length(), 1) }

    #try_parse

    类似 parse,但遇到未闭合引号时返回 Err(ParseError) 而非静默忽略。

    test "try_parse error" { let result = @dotenv.try_parse("KEY='unclosed") assert_true(result is Err(_)) }

    #defaults

    为缺失的键设置默认值,不影响已存在的键。

    test "defaults fills missing" { let env = @dotenv.parse("HOST=localhost") let defs = Map([("PORT", "5432"), ("HOST", "127.0.0.1")], capacity=2) let result = @dotenv.defaults(env, defs) assert_eq(result["HOST"], "localhost") assert_eq(result["PORT"], "5432") }

    #validate

    校验必需的 key 是否存在,返回缺失的 key 列表。

    test "validate finds missing keys" { let env = @dotenv.parse("HOST=localhost") let missing = @dotenv.validate(env, ["HOST", "PORT", "DB"]) assert_eq(missing.length(), 2) }

    #$$ 转义

    $$ 在 expand 中表示字面量 $,不会触发变量展开:

    test "dollar dollar escape" { let parsed = @dotenv.parse("PRICE=$$100") let result = @dotenv.expand(parsed) assert_eq(result["PRICE"], "$100") }

    #API 参考

    函数签名说明
    parse(String) -> Map[String, String]将 .env 字符串解析为键值对 Map(自动剥离 UTF-8 BOM)
    try_parse(String) -> Result[Map[String, String], ParseError]解析,遇到错误返回 Err
    expand(Map[String, String], env? : Map[String, String]) -> Map[String, String]展开 $VAR / ${VAR},$$ 表示字面量 $
    stringify(Map[String, String]) -> String将 Map 序列化为 .env 格式字符串
    diff(Map[String, String], Map[String, String]) -> DiffResult对比两个 Map,返回 added/removed/changed
    defaults(Map[String, String], Map[String, String]) -> Map[String, String]为缺失的键设置默认值
    validate(Map[String, String], Array[String]) -> Array[String]校验必需 key,返回缺失列表
    config(String, env? : Map[String, String], force? : Bool) -> Map[String, String]解析并合并环境变量,可选 force 覆盖
    merge(Map[String, String], Map[String, String], force? : Bool) -> Map[String, String]合并两个 Map
    load_multi(Array[String]) -> Map[String, String]解析多个 .env 字符串并合并
    config_multi(Array[String], env? : Map[String, String], force? : Bool) -> Map[String, String]多文件解析合并,可与外部 env 合并
    populate(Map[String, String], Map[String, String], force? : Bool) -> Int将解析结果写入目标 Map,返回写入数量

    #许可证

    Apache-2.0

    DiffResult

    pub struct DiffResult {
    added : Array[String]
    removed : Array[String]
    changed : Array[String]
    }

    Result of comparing two environment maps.

    ParseError

    pub struct ParseError {
    message : String
    line : Int
    }

    impl Show for ParseError

    config

    fn config(src : String, env? : Map[String, String], force? : Bool) -> Map[String, String]

    Parse a .env string and optionally merge with an existing environment.

    When force is true, parsed values overwrite existing keys in env. When false (default), existing keys in env are preserved.

    config_multi

    fn config_multi(sources : Array[String], env? : Map[String, String], force? : Bool) -> Map[String, String]

    Like load_multi, but also merge with an existing environment. When force is true, parsed values overwrite existing keys.

    defaults

    fn defaults(map : Map[String, String], default_map : Map[String, String]) -> Map[String, String]

    Set default values for keys that are missing in map.

    Keys from default_map are only added when they do not already exist in map. Returns a new map with defaults applied.

    diff

    fn diff(base : Map[String, String], other : Map[String, String]) -> DiffResult

    Compare two maps and return which keys were added, removed, or changed.

    • added: keys present in other but not in base
    • removed: keys present in base but not in other
    • changed: keys present in both but with different values

    expand

    fn expand(parsed : Map[String, String], env? : Map[String, String]) -> Map[String, String]

    Expand $VAR and ${VAR} references in all values of a parsed map.

    Variables are resolved from the parsed map itself first, then from the provided env map (e.g. system environment variables).

    If a referenced variable is not found, it is left as-is.

    load_multi

    fn load_multi(sources : Array[String]) -> Map[String, String]

    Parse multiple .env strings and merge them in order. Later files override earlier ones.

    merge

    fn merge(base : Map[String, String], other : Map[String, String], force? : Bool) -> Map[String, String]

    Merge two maps. Keys from other are added to base. When force is true, other wins on conflicts. When false, base wins on conflicts.

    parse

    fn parse(src : String) -> Map[String, String]

    Parse a .env file string into a Map of key-value pairs.

    Supports:
    • UTF-8 BOM (U+FEFF) is silently stripped
    • KEY=value and KEY: value syntax
    • export KEY=value prefix
    • Single-quoted values (literal, no escape processing)
    • Double-quoted values (with \n, \r, \t, \\ escape sequences)
    • Backtick-quoted values (literal, no escape processing)
    • Inline comments with #
    • Multiline values within quotes
    • Empty values
    • Keys and values surrounded by whitespace

    Last duplicate key wins. Malformed quotes are silently ignored.

    populate

    fn populate(target : Map[String, String], parsed : Map[String, String], force? : Bool) -> Int

    Write parsed key-value pairs into a target map.

    When force is true, parsed values overwrite existing keys. When false (default), existing keys in target are preserved. Returns the number of keys actually written.

    stringify

    fn stringify(map : Map[String, String]) -> String

    Serialize a Map back into .env format string.

    Values containing spaces, #, ", ', backticks, \n, \r, or \t are double-quoted with proper escape sequences. Simple values are written unquoted.

    try_parse

    fn try_parse(src : String) -> Result[Map[String, String], ParseError]

    Like parse, but returns Err(ParseError) on malformed input (e.g. unclosed quotes).

    validate

    fn validate(map : Map[String, String], required : Array[String]) -> Array[String]

    Check that all required keys are present in map. Returns an array of missing keys. Empty array means all present.