<commit_before>#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>

#include "args.h"
#include "list.h"
#include "rule.h"

const unsigned ARGS_QUEUE_BUFFER_NONE = 0;

/* Enum representing the various state of the parser */
enum parse_state {
    NO_STATE,     // Ready for anything!
    RULE,         // expecting a rule matcher (like "artist")
    RULE_VALUE,   // expecting rule value (like "modest mouse")
    QUEUE,        // expecting "queue_only" int value
    IFILE,        // expecting song list input file
    QUEUE_BUFFER  // expecting queue buffer value
};

/* check and see if 'to_check' matches any of 'count' given
 * arguments */
bool check_flags(const char * to_check, unsigned count, ...) {
    va_list args;
    const char * current;
    va_start(args, count);
    bool out = false;
    for (; count > 0; count--) {
        current = va_arg(args, char *);
        if (strcasecmp(to_check, current) == 0) {
            out = true; /* don't break, we need to process the arguments */
        }
    }
    va_end(args);
    return out;
}

/* get the enum rule_type type from the option if possible.
 * Otherwise, return -1 */
int rule_type_from_flag(char * option) {
   if (check_flags(option, 2, "--exclude", "-e")) {
        return RULE_EXCLUDE;
    } else {
        return -1;
    }
}

/* check and see if we can transition to a new top-level
 * state from our current state */
bool state_can_trans(enum parse_state state) {
    if (state == NO_STATE || state == RULE) { return true; }
    return false;
}

/* if we're in a correct state, then add the rule to the
 * ruleset in the list of options */
int flush_rule(enum parse_state state, 
               struct ashuffle_options * opts, 
               struct song_rule * rule) {
    if (state == RULE && rule->matchers.length > 0) {
        /* add the rule to the ruleset */
        list_push(&opts->ruleset, node_from(rule, sizeof(struct song_rule)));
    }
    return 0;
}

int ashuffle_init(struct ashuffle_options * opts) {
    opts->queue_only = 0;
    opts->file_in = NULL;
    opts->check_uris = true;
    list_init(&opts->ruleset);
    opts->queue_buffer = ARGS_QUEUE_BUFFER_NONE; // 0
    return 0;
}

int ashuffle_options(struct ashuffle_options * opts, 
                     int argc, char * argv[]) {
    /* State for the state machine */
    enum parse_state state = NO_STATE;
    bool transable = false;

    char * match_field = NULL;
    struct song_rule rule;

    int type_flag = -1;
    
    for (int i = 1; i < argc; i++) {
        transable = state_can_trans(state);
        if (transable) {
            type_flag = rule_type_from_flag(argv[i]);
        }

        /* check we should print the help text */
        if (check_flags(argv[i], 3, "--help", "-h", "-?")) {
            return -1;
        } else if (type_flag != -1) {
            flush_rule(state, opts, &rule);
            rule_init(&rule, type_flag);
            type_flag = -1;
            state = RULE;
        } else if (transable && check_flags(argv[i], 2, "--nocheck", "-n")) {
            flush_rule(state, opts, &rule);
            opts->check_uris = false;
            state = NO_STATE;
        } else if (transable && check_flags(argv[i], 1, "--queue_buffer")) {
            flush_rule(state, opts, &rule);
            state = QUEUE_BUFFER;
        } else if (transable && opts->queue_only == 0 && check_flags(argv[i], 2, "--only", "-o")) {
            flush_rule(state, opts, &rule);
            state = QUEUE;
        } else if (transable && opts->file_in == NULL && check_flags(argv[i], 2, "--file", "-f")) {
            flush_rule(state, opts, &rule);
            state = IFILE;
        } else if (state == RULE) {
            match_field = argv[i];
            state = RULE_VALUE;
        } else if (state == RULE_VALUE) {
            rule_add_criteria(&rule, match_field, argv[i]);
            match_field = NULL;
            state = RULE;
        } else if (state == QUEUE) {
            opts->queue_only = (unsigned) strtoul(argv[i], NULL, 10);
            /* Make sure we got a valid queue number */
            if (errno == EINVAL || errno == ERANGE) {
                fputs("Error converting queue length to integer.\n", stderr);
                return -1;
            }
            state = NO_STATE;
        } else if (state == IFILE) {
            if (check_flags(argv[i], 1, "-")) {
                opts->file_in = stdin;
            } else {
                opts->file_in = fopen(argv[i], "r");
            }
            state = NO_STATE;
        } else if (state == QUEUE_BUFFER) {
            opts->queue_buffer = (unsigned) strtoul(argv[i], NULL, 10);
            if (errno == EINVAL || errno == ERANGE) {
                fputs("Error converting queue buffer length to integer.\n", stderr);
                return -1;
            }
            state = NO_STATE;
        } else {
            fprintf(stderr, "Invalid option: %s.\n", argv[i]);
            return -1;
        }
    }

    if (state == RULE_VALUE) {
        fprintf(stderr, "No value supplied for match '%s'.\n", match_field);
        return -1;<commit_msg>args: fix strtoul error checks<commit_after>#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

#include "args.h"
#include "list.h"
#include "rule.h"

const unsigned ARGS_QUEUE_BUFFER_NONE = 0;

/* Enum representing the various state of the parser */
enum parse_state {
    NO_STATE,     // Ready for anything!
    RULE,         // expecting a rule matcher (like "artist")
    RULE_VALUE,   // expecting rule value (like "modest mouse")
    QUEUE,        // expecting "queue_only" int value
    IFILE,        // expecting song list input file
    QUEUE_BUFFER  // expecting queue buffer value
};

/* check and see if 'to_check' matches any of 'count' given
 * arguments */
bool check_flags(const char * to_check, unsigned count, ...) {
    va_list args;
    const char * current;
    va_start(args, count);
    bool out = false;
    for (; count > 0; count--) {
        current = va_arg(args, char *);
        if (strcasecmp(to_check, current) == 0) {
            out = true; /* don't break, we need to process the arguments */
        }
    }
    va_end(args);
    return out;
}

/* get the enum rule_type type from the option if possible.
 * Otherwise, return -1 */
int rule_type_from_flag(char * option) {
   if (check_flags(option, 2, "--exclude", "-e")) {
        return RULE_EXCLUDE;
    } else {
        return -1;
    }
}

/* check and see if we can transition to a new top-level
 * state from our current state */
bool state_can_trans(enum parse_state state) {
    if (state == NO_STATE || state == RULE) { return true; }
    return false;
}

/* if we're in a correct state, then add the rule to the
 * ruleset in the list of options */
int flush_rule(enum parse_state state, 
               struct ashuffle_options * opts, 
               struct song_rule * rule) {
    if (state == RULE && rule->matchers.length > 0) {
        /* add the rule to the ruleset */
        list_push(&opts->ruleset, node_from(rule, sizeof(struct song_rule)));
    }
    return 0;
}

int ashuffle_init(struct ashuffle_options * opts) {
    opts->queue_only = 0;
    opts->file_in = NULL;
    opts->check_uris = true;
    list_init(&opts->ruleset);
    opts->queue_buffer = ARGS_QUEUE_BUFFER_NONE; // 0
    return 0;
}

/* "safe" string to unsigned conversion
 * unlike strtoul, considers partial matches e.g. "42foo" or "" as errors.
 * returns UINT_MAX and sets errno on error.
 */
unsigned strtou(char * str) {
    char * endptr;
    unsigned long value;

    value = strtoul(str, &endptr, 10);

    if (endptr == str || endptr[0] != '\0') {
        errno = EINVAL;
        return UINT_MAX;
    }

    if (value > UINT_MAX) {
        errno = ERANGE;
        return UINT_MAX;
    }

    return (unsigned) value;
}

int ashuffle_options(struct ashuffle_options * opts, 
                     int argc, char * argv[]) {
    /* State for the state machine */
    enum parse_state state = NO_STATE;
    bool transable = false;

    char * match_field = NULL;
    struct song_rule rule;

    int type_flag = -1;
    
    for (int i = 1; i < argc; i++) {
        transable = state_can_trans(state);
        if (transable) {
            type_flag = rule_type_from_flag(argv[i]);
        }

        /* check we should print the help text */
        if (check_flags(argv[i], 3, "--help", "-h", "-?")) {
            return -1;
        } else if (type_flag != -1) {
            flush_rule(state, opts, &rule);
            rule_init(&rule, type_flag);
            type_flag = -1;
            state = RULE;
        } else if (transable && check_flags(argv[i], 2, "--nocheck", "-n")) {
            flush_rule(state, opts, &rule);
            opts->check_uris = false;
            state = NO_STATE;
        } else if (transable && check_flags(argv[i], 1, "--queue_buffer")) {
            flush_rule(state, opts, &rule);
            state = QUEUE_BUFFER;
        } else if (transable && opts->queue_only == 0 && check_flags(argv[i], 2, "--only", "-o")) {
            flush_rule(state, opts, &rule);
            state = QUEUE;
        } else if (transable && opts->file_in == NULL && check_flags(argv[i], 2, "--file", "-f")) {
            flush_rule(state, opts, &rule);
            state = IFILE;
        } else if (state == RULE) {
            match_field = argv[i];
            state = RULE_VALUE;
        } else if (state == RULE_VALUE) {
            rule_add_criteria(&rule, match_field, argv[i]);
            match_field = NULL;
            state = RULE;
        } else if (state == QUEUE) {
            opts->queue_only = strtou(argv[i]);
            if (opts->queue_only == UINT_MAX) {
                fprintf(stderr, "Error converting queue length '%s' to integer.\n",
                        argv[i]);
                return -1;
            }
            state = NO_STATE;
        } else if (state == IFILE) {
            if (check_flags(argv[i], 1, "-")) {
                opts->file_in = stdin;
            } else {
                opts->file_in = fopen(argv[i], "r");
            }
            state = NO_STATE;
        } else if (state == QUEUE_BUFFER) {
            opts->queue_buffer = strtou(argv[i]);
            if (opts->queue_buffer == UINT_MAX) {
                fprintf(stderr, "Error converting queue buffer length '%s' to integer.\n",
                        argv[i]);
                return -1;
            }
            state = NO_STATE;
        } else {
            fprintf(stderr, "Invalid option: %s.\n", argv[i]);
            return -1;
        }
    }

    if (state == RULE_VALUE) {
        fprintf(stderr, "No value supplied for match '%s'.\n", match_field);
        return -1;
    } else if (!state_can_trans(state)) {
        fprintf(stderr, "No argument supplied for '%s'.\n", argv[argc - 1]);
        return -1;
{'fraction_non_alphanumeric': 0.1318082788671024, 'fraction_numerical': 0.005991285403050109, 'mean_word_length': 1.9970076169749729, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5745040', 'n_tokens_mistral': 3510, 'n_tokens_neox': 3401, 'n_words': 999}