299 lines
6.4 KiB
C
299 lines
6.4 KiB
C
/*
|
|
* Copyright (c) Rhizome <rhizome@wip.sh>
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
#include "rotagen.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define MAX_LINE_LENGTH 64
|
|
|
|
enum conf_section {
|
|
CONF_SECTION_PEOPLE,
|
|
CONF_SECTION_JOBS,
|
|
CONF_SECTION_SLOTS,
|
|
CONF_SECTION_CONSTRAINTS,
|
|
|
|
CONF_SECTION_COUNT,
|
|
};
|
|
|
|
static const char *section_names[CONF_SECTION_COUNT] = {
|
|
[CONF_SECTION_PEOPLE] = "people",
|
|
[CONF_SECTION_JOBS] = "jobs",
|
|
[CONF_SECTION_SLOTS] = "slots",
|
|
[CONF_SECTION_CONSTRAINTS] = "constraints",
|
|
};
|
|
|
|
static FILE *stream;
|
|
static int line_len;
|
|
static char line[MAX_LINE_LENGTH];
|
|
static bool section_read[CONF_SECTION_COUNT];
|
|
|
|
static void next_line(void);
|
|
static bool line_is_heading(void);
|
|
static enum conf_section read_heading(void);
|
|
static bool all_sections(void);
|
|
static void read_people(struct config *conf_out);
|
|
static void read_jobs(struct config *conf_out);
|
|
static void read_slots(struct config *conf_out);
|
|
static void read_constraints(struct config *conf_out);
|
|
static bool parse_constraint(const struct config *conf,
|
|
char *input,
|
|
int len,
|
|
struct constraint *out);
|
|
|
|
void read_config(const char *path, struct config *config_out)
|
|
{
|
|
stream = fopen(path, "r");
|
|
if (stream == NULL) {
|
|
fprintf(stderr, "Fatal error: can't open config file %s\n", path);
|
|
exit(1);
|
|
}
|
|
|
|
memset(section_read, 0, sizeof(section_read));
|
|
|
|
do
|
|
next_line();
|
|
while (!line_is_heading());
|
|
do {
|
|
const enum conf_section section = read_heading();
|
|
switch (section) {
|
|
case CONF_SECTION_PEOPLE:
|
|
read_people(config_out);
|
|
break;
|
|
case CONF_SECTION_JOBS:
|
|
read_jobs(config_out);
|
|
break;
|
|
case CONF_SECTION_SLOTS:
|
|
read_slots(config_out);
|
|
break;
|
|
case CONF_SECTION_CONSTRAINTS:
|
|
read_constraints(config_out);
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Warning: unhandled conf section %d\n", section);
|
|
continue;
|
|
}
|
|
section_read[section] = true;
|
|
} while (!all_sections());
|
|
|
|
fclose(stream);
|
|
}
|
|
|
|
static void next_line(void)
|
|
{
|
|
line_len = 0;
|
|
int c;
|
|
while (1) {
|
|
if (line_len >= MAX_LINE_LENGTH) {
|
|
fprintf(stderr,
|
|
"Warning: line length limit reached, line truncated\n");
|
|
return;
|
|
}
|
|
c = fgetc(stream);
|
|
if (c == '\n' || c == EOF) {
|
|
line[line_len] = '\0';
|
|
return;
|
|
}
|
|
line[line_len++] = c;
|
|
}
|
|
}
|
|
|
|
static bool line_is_heading(void)
|
|
{
|
|
return line[0] == '[' && line[line_len - 1] == ']';
|
|
}
|
|
|
|
static enum conf_section read_heading(void)
|
|
{
|
|
for (int i = 0; i < CONF_SECTION_COUNT; ++i) {
|
|
if (strncasecmp(section_names[i], &line[1], line_len - 2) == 0)
|
|
return (enum conf_section)i;
|
|
}
|
|
fprintf(stderr, "Fatal error: unexpected section %s\n", line);
|
|
exit(1);
|
|
}
|
|
|
|
static bool all_sections(void)
|
|
{
|
|
for (int i = 0; i < CONF_SECTION_COUNT; ++i) {
|
|
if (!section_read[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void read_people(struct config *conf_out)
|
|
{
|
|
while (1) {
|
|
next_line();
|
|
if (line_is_heading()) {
|
|
return;
|
|
} else if (line_len == 0) {
|
|
if (feof(stream))
|
|
return;
|
|
else
|
|
continue;
|
|
}
|
|
strcpy(conf_out->people[conf_out->num_people++], line);
|
|
}
|
|
}
|
|
|
|
static void read_jobs(struct config *conf_out)
|
|
{
|
|
while (1) {
|
|
next_line();
|
|
if (line_is_heading()) {
|
|
return;
|
|
} else if (line_len == 0) {
|
|
if (feof(stream))
|
|
return;
|
|
else
|
|
continue;
|
|
}
|
|
strcpy(conf_out->jobs[conf_out->num_jobs++], line);
|
|
}
|
|
}
|
|
|
|
static void read_slots(struct config *conf_out)
|
|
{
|
|
while (1) {
|
|
next_line();
|
|
if (line_is_heading()) {
|
|
return;
|
|
} else if (line_len == 0) {
|
|
if (feof(stream))
|
|
return;
|
|
else
|
|
continue;
|
|
}
|
|
strcpy(conf_out->slots[conf_out->num_slots++], line);
|
|
}
|
|
}
|
|
|
|
static void read_constraints(struct config *conf_out)
|
|
{
|
|
while (1) {
|
|
next_line();
|
|
if (line_is_heading()) {
|
|
return;
|
|
} else if (line_len == 0) {
|
|
if (feof(stream))
|
|
return;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
struct constraint *out
|
|
= &conf_out->constraints[conf_out->num_constraints];
|
|
if (parse_constraint(conf_out, line, line_len, out))
|
|
++conf_out->num_constraints;
|
|
else
|
|
fprintf(
|
|
stderr,
|
|
"Warning: failed to parse constraint \"%s\"; will be ignored\n",
|
|
line);
|
|
}
|
|
}
|
|
|
|
static bool parse_constraint(const struct config *conf,
|
|
char *input,
|
|
int len,
|
|
struct constraint *out)
|
|
{
|
|
bool match = false;
|
|
for (int i = 0; i < conf->num_people; ++i) {
|
|
const int person_len = strlen(conf->people[i]);
|
|
if (strncasecmp(input, conf->people[i], person_len) == 0) {
|
|
out->person = i;
|
|
input += person_len + 1;
|
|
len -= person_len + 1;
|
|
match = true;
|
|
}
|
|
}
|
|
if (!match) {
|
|
fprintf(stderr, "Unknown person in constraint\n");
|
|
return false;
|
|
}
|
|
|
|
const char *filler = "can't do ";
|
|
const int filler_len = strlen(filler);
|
|
if (strncasecmp(input, filler, filler_len) == 0) {
|
|
input += filler_len;
|
|
len -= filler_len;
|
|
} else {
|
|
fprintf(stderr, "Invalid constraint syntax\n");
|
|
return false;
|
|
}
|
|
|
|
const char *job_str = "job ", *slot_str = "slot ";
|
|
const int job_str_len = strlen(job_str), slot_str_len = strlen(slot_str);
|
|
if (strncasecmp(input, job_str, job_str_len) == 0) {
|
|
out->type = JOB_EXEMPTION_CONSTRAINT;
|
|
input += job_str_len;
|
|
len -= job_str_len;
|
|
} else if (strncasecmp(input, slot_str, slot_str_len) == 0) {
|
|
out->type = SLOT_EXEMPTION_CONSTRAINT;
|
|
input += slot_str_len;
|
|
len -= slot_str_len;
|
|
} else {
|
|
fprintf(stderr, "Unknown constraint type\n");
|
|
return false;
|
|
}
|
|
|
|
if (input[0] == '"' && input[len - 1] == '"') {
|
|
++input;
|
|
len -= 2;
|
|
} else {
|
|
fprintf(stderr, "Missing quotes in constraint\n");
|
|
return false;
|
|
}
|
|
|
|
switch (out->type) {
|
|
case SLOT_EXEMPTION_CONSTRAINT:
|
|
match = false;
|
|
for (int i = 0; i < conf->num_slots; ++i) {
|
|
const int slot_len = strlen(conf->slots[i]);
|
|
if (strncasecmp(input, conf->slots[i], slot_len) == 0) {
|
|
out->object.slot = i;
|
|
input += slot_len + 1;
|
|
len -= slot_len + 1;
|
|
match = true;
|
|
}
|
|
}
|
|
if (!match) {
|
|
fprintf(stderr, "Unknown slot in constraint\n");
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case JOB_EXEMPTION_CONSTRAINT:
|
|
match = false;
|
|
for (int i = 0; i < conf->num_jobs; ++i) {
|
|
const int job_len = strlen(conf->jobs[i]);
|
|
if (strncasecmp(input, conf->jobs[i], job_len) == 0) {
|
|
out->object.job = i;
|
|
input += job_len + 1;
|
|
len -= job_len + 1;
|
|
match = true;
|
|
}
|
|
}
|
|
if (!match) {
|
|
fprintf(stderr, "Unknown job in constraint\n");
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "Warning: unhandled constraint type in %s()", __func__);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|