Skip to content

Commit

Permalink
impl dotenv
Browse files Browse the repository at this point in the history
  • Loading branch information
Hanaasagi committed Jun 3, 2023
1 parent 4df8585 commit c033606
Show file tree
Hide file tree
Showing 7 changed files with 901 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/zig-out/
**/zig-cache/
81 changes: 81 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const lib = b.addStaticLibrary(.{
.name = "dotenv",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/lib.zig" },
.target = target,
.optimize = optimize,
});
lib.linkSystemLibrary("c");

// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
b.installArtifact(lib);

// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const main_tests = b.addTest(.{
.name = "lib.zig",
.root_source_file = .{ .path = "src/lib.zig" },
.target = target,
.optimize = optimize,
});
main_tests.linkSystemLibrary("c");

const run_main_tests = b.addRunArtifact(main_tests);

const parser_tests = b.addTest(.{
.name = "parser.zig",
.root_source_file = .{ .path = "src/parser.zig" },
.target = target,
.optimize = optimize,
});
parser_tests.linkSystemLibrary("c");

const run_parser_tests = b.addRunArtifact(parser_tests);

const utils_tests = b.addTest(.{
.name = "utils.zig",
.root_source_file = .{ .path = "src/utils.zig" },
.target = target,
.optimize = optimize,
});
utils_tests.linkSystemLibrary("c");
const run_utils_tests = b.addRunArtifact(utils_tests);

const loader_tests = b.addTest(.{
.name = "loader.zig",
.root_source_file = .{ .path = "src/loader.zig" },
.target = target,
.optimize = optimize,
});
loader_tests.linkSystemLibrary("c");
const run_loader_tests = b.addRunArtifact(loader_tests);

// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build test`
// This will evaluate the `test` step rather than the default, which is "install".
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_main_tests.step);
test_step.dependOn(&run_parser_tests.step);
test_step.dependOn(&run_utils_tests.step);
test_step.dependOn(&run_loader_tests.step);
}
70 changes: 70 additions & 0 deletions src/lib.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const std = @import("std");
const testing = std.testing;
const FileFinder = @import("./utils.zig").FileFinder;
const Loader = @import("./loader.zig").Loader;
const Options = @import("./loader.zig").Options;

/// Loads the `.env*` file from the current directory or parents.
///
/// If variables with the same names already exist in the environment, then their values will be
/// preserved when `options.override = false` (default behavior)
///
/// If you wish to ensure all variables are loaded from your `.env` file, ignoring variables
/// already existing in the environment, then pass `options.override = true`
///
/// Where multiple declarations for the same environment variable exist in `.env`
/// file, the *first one* will be applied.
pub fn load(allocator: std.mem.Allocator, options: Options) !void {
var finder = FileFinder;
const path = try finder.find(allocator);

try loadFrom(path, options);
}

/// Loads the `.env*` file from the given path.
pub fn loadFrom(allocator: std.mem.Allocator, path: []const u8, options: Options) !void {
var f = try std.fs.cwd().openFile(path, .{});
defer f.close();

var br = std.io.bufferedReader(f.reader());
var reader = br.reader();

var loader = Loader.init(allocator, options);
defer loader.deinit();
try loader.load(reader);
}

test "test load real file" {
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR1") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR2") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR3") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR4") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR5") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR6") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR7") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR8") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR9") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR10") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_VAR11") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_MULTILINE1") == null);
try testing.expect(std.os.getenv("CODEGEN_TEST_MULTILINE2") == null);

try loadFrom(testing.allocator, "./testdata/.env", .{});

try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR1").?, "hello!");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR2").?, "'quotes within quotes'");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR3").?, "double quoted with # hash in value");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR4").?, "single quoted with # hash in value");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR5").?, "not_quoted_with_#_hash_in_value");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR6").?, "not_quoted_with_comment_beheind");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR7").?, "not quoted with escaped space");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR8").?, "double quoted with comment beheind");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR9").?, "Variable starts with a whitespace");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR10").?, "Value starts with a whitespace after =");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_VAR11").?, "Variable ends with a whitespace before =");
try testing.expectEqualStrings(std.os.getenv("CODEGEN_TEST_MULTILINE1").?, "First Line\nSecond Line");
try testing.expectEqualStrings(
std.os.getenv("CODEGEN_TEST_MULTILINE2").?,
"# First Line Comment\nSecond Line\n#Third Line Comment\nFourth Line\n",
);
}
Loading

0 comments on commit c033606

Please sign in to comment.