~novakane/agertu

agertu/src/Config.zig -rw-r--r-- 5.6 KiB
9ea83d8fHugo Machet build: Bump to version 2.0.0-dev 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// This file is part of agertu
//
// Copyright (C) 2021-2022 Hugo Machet
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

const std = @import("std");
const fs = std.fs;
const mem = std.mem;
const os = std.os;

const ini = @import("ini");

const util = @import("util.zig");
const Config = @This();

const log = std.log.scoped(.config);

arena: std.heap.ArenaAllocator = undefined,

// Surface
surface_borders_size: u16 = 0,

// <top>:<right>:<bottom>:<left>
surface_anchors: []const u8 = "0:1:1:0",
surface_margins: []const u8 = "0:10:10:0",

surface_background_color: []const u8 = "0x202325",
surface_borders_color: []const u8 = "0x9e2f59",

// Tags
tags_amount: u32 = 9,
tags_number_text: bool = true,

tags_square_size: u16 = 50,
tags_borders_size: u16 = 2,
tags_margins: u16 = 5,

tags_background_color: []const u8 = "0x3a4043",
tags_foreground_color: []const u8 = "0xdce1e4",
tags_border_colors: []const u8 = "0x646e73",

tags_focused_background_color: []const u8 = "0xf9f9fa",
tags_focused_foreground_color: []const u8 = "0x202325",
tags_focused_borders_color: []const u8 = "0xdce1e4",

tags_occupied_background_color: []const u8 = "0x2e3f9f",
tags_occupied_foreground_color: []const u8 = "0xdce1e4",
tags_occupied_borders_color: []const u8 = "0xdbf1fd",

tags_urgent_background_color: []const u8 = "0x9e2f59",
tags_urgent_foreground_color: []const u8 = "0x202325",
tags_urgent_borders_color: []const u8 = "0xfde7f6",

// Font
fonts: []const u8 = "monospace:size=18",

pub fn init(config: *Config) !void {
    config.arena = std.heap.ArenaAllocator.init(util.gpa);
    errdefer config.arena.deinit();

    const path: ?[]const u8 = blk: {
        if (os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| {
            break :blk try fs.path.join(
                util.gpa,
                &[_][]const u8{ xdg_config_home, "agertu/config.ini" },
            );
        } else if (os.getenv("HOME")) |home| {
            break :blk try fs.path.join(
                util.gpa,
                &[_][]const u8{ home, ".config/agertu/config.ini" },
            );
        } else break :blk null;
    };
    if (path == null) return;
    defer util.gpa.free(path.?);

    // All fields in Config have default values so we don't want to return an
    // error if the file is not found, just use the default.
    os.access(path.?, os.R_OK) catch |err| switch (err) {
        os.AccessError.FileNotFound => {
            log.warn("Config file not found, using default values.", .{});
            return;
        },
        else => return err,
    };
    const file = fs.cwd().openFile(path.?, .{}) catch |err| switch (err) {
        fs.File.OpenError.FileNotFound => {
            log.warn("Config file not found, using default values.", .{});
            return;
        },
        else => return err,
    };
    defer file.close();

    var buf = std.io.bufferedReader(file.reader());
    var it = ini.tokenize(buf.reader());
    var line: usize = 0;
    while (it.next(&line) catch |err| switch (err) {
        error.InvalidLine => {
            util.fatal(.config, "{s}:{}: Syntax error.", .{ path, line });
        },
        else => return err,
    }) |content| switch (content) {
        .section => util.fatal(.config, "{s}:{}: Sections are not yet allowed.", .{ path, line }),
        .assign => |as| {
            assign(config, as.variable, as.value) catch |err| switch (err) {
                ParseError.InvalidBoolean => util.fatal(
                    .config,
                    "{s}:{} - {s}: Must be `true` or `false`.",
                    .{ path, line, err },
                ),
                ParseError.UnknownType => continue,
                else => util.fatal(.config, "{s}:{} - {s}", .{ path, line, err }),
            };
        },
    };
}

pub fn deinit(config: *Config) void {
    config.arena.deinit();
}

/// Loop through a Config struct fields and if one is equal to a variable from
/// the config file, parse and assign the value to it.
fn assign(config: *Config, variable: []const u8, value: []const u8) !void {
    inline for (@typeInfo(Config).Struct.fields) |field| {
        if (mem.eql(u8, variable, field.name)) {
            @field(config, field.name) =
                try parse(config.arena.allocator(), @TypeOf(@field(config, field.name)), value);
            return;
        }
    }
    return error.BadConfig;
}

const ParseError = error{
    /// Must be defined in `bool_options`.
    InvalidBoolean,
    /// Ignore unknown types. Useful to add fields like arena in the Config struct.
    UnknownType,
};

/// Boolean options authorized in the config file.
const bool_options = std.ComptimeStringMap(bool, .{
    .{ "true", true },
    .{ "false", false },
});

// Parse a value from the config file to a comptime known type needed for this
// config options.
fn parse(alloc: std.mem.Allocator, comptime T: type, value: []const u8) anyerror!T {
    try switch (T) {
        u16, u32 => return try std.fmt.parseUnsigned(T, value, 10),
        []const u8 => return alloc.dupe(u8, value),
        bool => return bool_options.get(value) orelse return ParseError.InvalidBoolean,
        else => return ParseError.UnknownType,
    };
}