// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
/**
 * Load environment variables from `.env` files.
 *
 * @module
 */ import { difference, removeEmptyValues } from "./util.ts";
const RE_KeyValue = /^\s*(?:export\s+)?(?<key>[a-zA-Z_]+[a-zA-Z0-9_]*?)\s*=[\ \t]*('\n?(?<notInterpolated>(.|\n)*?)\n?'|"\n?(?<interpolated>(.|\n)*?)\n?"|(?<unquoted>[^\n#]*)) *#*.*$/gm;
const RE_ExpandValue = /(\${(?<inBrackets>.+?)(\:-(?<inBracketsDefault>.+))?}|(?<!\\)\$(?<notInBrackets>\w+)(\:-(?<notInBracketsDefault>.+))?)/g;
export function parse(rawDotenv, restrictEnvAccessTo = []) {
    const env = {};
    let match;
    const keysForExpandCheck = [];
    while((match = RE_KeyValue.exec(rawDotenv)) != null){
        const { key , interpolated , notInterpolated , unquoted  } = match?.groups;
        if (unquoted) {
            keysForExpandCheck.push(key);
        }
        env[key] = typeof notInterpolated === "string" ? notInterpolated : typeof interpolated === "string" ? expandCharacters(interpolated) : unquoted.trim();
    }
    //https://github.com/motdotla/dotenv-expand/blob/ed5fea5bf517a09fd743ce2c63150e88c8a5f6d1/lib/main.js#L23
    const variablesMap = {
        ...env,
        ...readEnv(restrictEnvAccessTo)
    };
    keysForExpandCheck.forEach((key)=>{
        env[key] = expand(env[key], variablesMap);
    });
    return env;
}
const defaultConfigOptions = {
    path: `.env`,
    export: false,
    safe: false,
    example: `.env.example`,
    allowEmptyValues: false,
    defaults: `.env.defaults`,
    restrictEnvAccessTo: []
};
export function configSync(options = {}) {
    const o = {
        ...defaultConfigOptions,
        ...options
    };
    const conf = parseFile(o.path, o.restrictEnvAccessTo);
    if (o.defaults) {
        const confDefaults = parseFile(o.defaults, o.restrictEnvAccessTo);
        for(const key in confDefaults){
            if (!(key in conf)) {
                conf[key] = confDefaults[key];
            }
        }
    }
    if (o.safe) {
        const confExample = parseFile(o.example, o.restrictEnvAccessTo);
        assertSafe(conf, confExample, o.allowEmptyValues, o.restrictEnvAccessTo);
    }
    if (o.export) {
        for(const key1 in conf){
            if (Deno.env.get(key1) !== undefined) continue;
            Deno.env.set(key1, conf[key1]);
        }
    }
    return conf;
}
export async function config(options = {}) {
    const o = {
        ...defaultConfigOptions,
        ...options
    };
    const conf = await parseFileAsync(o.path, o.restrictEnvAccessTo);
    if (o.defaults) {
        const confDefaults = await parseFileAsync(o.defaults, o.restrictEnvAccessTo);
        for(const key in confDefaults){
            if (!(key in conf)) {
                conf[key] = confDefaults[key];
            }
        }
    }
    if (o.safe) {
        const confExample = await parseFileAsync(o.example, o.restrictEnvAccessTo);
        assertSafe(conf, confExample, o.allowEmptyValues, o.restrictEnvAccessTo);
    }
    if (o.export) {
        for(const key1 in conf){
            if (Deno.env.get(key1) !== undefined) continue;
            Deno.env.set(key1, conf[key1]);
        }
    }
    return conf;
}
function parseFile(filepath, restrictEnvAccessTo = []) {
    try {
        return parse(new TextDecoder("utf-8").decode(Deno.readFileSync(filepath)), restrictEnvAccessTo);
    } catch (e) {
        if (e instanceof Deno.errors.NotFound) return {};
        throw e;
    }
}
async function parseFileAsync(filepath, restrictEnvAccessTo = []) {
    try {
        return parse(new TextDecoder("utf-8").decode(await Deno.readFile(filepath)), restrictEnvAccessTo);
    } catch (e) {
        if (e instanceof Deno.errors.NotFound) return {};
        throw e;
    }
}
function expandCharacters(str) {
    const charactersMap = {
        "\\n": "\n",
        "\\r": "\r",
        "\\t": "\t"
    };
    return str.replace(/\\([nrt])/g, ($1)=>charactersMap[$1]);
}
function assertSafe(conf, confExample, allowEmptyValues, restrictEnvAccessTo = []) {
    const currentEnv = readEnv(restrictEnvAccessTo);
    // Not all the variables have to be defined in .env, they can be supplied externally
    const confWithEnv = Object.assign({}, currentEnv, conf);
    const missing = difference(Object.keys(confExample), // If allowEmptyValues is false, filter out empty values from configuration
    Object.keys(allowEmptyValues ? confWithEnv : removeEmptyValues(confWithEnv)));
    if (missing.length > 0) {
        const errorMessages = [
            `The following variables were defined in the example file but are not present in the environment:\n  ${missing.join(", ")}`,
            `Make sure to add them to your env file.`,
            !allowEmptyValues && `If you expect any of these variables to be empty, you can set the allowEmptyValues option to true.`
        ];
        throw new MissingEnvVarsError(errorMessages.filter(Boolean).join("\n\n"), missing);
    }
}
// a guarded env access, that reads only a subset from the Deno.env object,
// if `restrictEnvAccessTo` property is passed.
function readEnv(restrictEnvAccessTo) {
    if (restrictEnvAccessTo && Array.isArray(restrictEnvAccessTo) && restrictEnvAccessTo.length > 0) {
        return restrictEnvAccessTo.reduce((accessedEnvVars, envVarName)=>{
            if (Deno.env.get(envVarName)) {
                accessedEnvVars[envVarName] = Deno.env.get(envVarName);
            }
            return accessedEnvVars;
        }, {});
    }
    return Deno.env.toObject();
}
export class MissingEnvVarsError extends Error {
    missing;
    constructor(message, missing){
        super(message);
        this.name = "MissingEnvVarsError";
        this.missing = missing;
        Object.setPrototypeOf(this, new.target.prototype);
    }
}
function expand(str, variablesMap) {
    if (RE_ExpandValue.test(str)) {
        return expand(str.replace(RE_ExpandValue, function(...params) {
            const { inBrackets , inBracketsDefault , notInBrackets , notInBracketsDefault  } = params[params.length - 1];
            const expandValue = inBrackets || notInBrackets;
            const defaultValue = inBracketsDefault || notInBracketsDefault;
            return variablesMap[expandValue] || expand(defaultValue, variablesMap);
        }), variablesMap);
    } else {
        return str;
    }
}
/**
 * @param object object to be stringified
 * @returns string of object
 * ```ts
 * import { stringify } from "https://deno.land/std@$STD_VERSION/dotenv/mod.ts";
 *
 * const object = { GREETING: "hello world" };
 * const string = stringify(object);
 * ```
 */ export function stringify(object) {
    const lines = [];
    for (const [key, value] of Object.entries(object)){
        let quote;
        let escapedValue = value ?? "";
        if (key.startsWith("#")) {
            console.warn(`key starts with a '#' indicates a comment and is ignored: '${key}'`);
            continue;
        } else if (escapedValue.includes("\n")) {
            // escape inner new lines
            escapedValue = escapedValue.replaceAll("\n", "\\n");
            quote = `"`;
        } else if (escapedValue.match(/\W/)) {
            quote = "'";
        }
        if (quote) {
            // escape inner quotes
            escapedValue = escapedValue.replaceAll(quote, `\\${quote}`);
            escapedValue = `${quote}${escapedValue}${quote}`;
        }
        const line = `${key}=${escapedValue}`;
        lines.push(line);
    }
    return lines.join("\n");
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 18132 characters]