#include <ctype.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "array.h"
#include "misc.h"
#include "utf8.h"
/* Natural string comparison: case insensitive and compare digit sequences
inside the strings as numbers */
static int strnatcmp(const void *a, const void *b)
{
const char *s1 = *(const char **)a, *s2 = *(const char **)b;
uint32_t c1 = 0, c2 = 0;
do
{
if (isdigit(*s1) && isdigit(*s2))
{
char *end1, *end2;
const uint64_t n1 = strtoull(s1, &end1, 10);
const uint64_t n2 = strtoull(s2, &end2, 10);
if (n1 != n2)
{
return n1 > n2 ? 1 :
n1 < n2 ? -1 : 0;
}
s1 = end1;
s2 = end2;
continue;
}
else
{
c1 = codep_tolower(xutf8_decode_step(&s1));
c2 = codep_tolower(xutf8_decode_step(&s2));
}
}
while (c1 == c2 && c1);
return c1 > c2 ? 1 :
c1 < c2 ? -1 : 0;
}
NORETURN void usage(int exit_status)
{
printf(
"NAME\n"
" %s - Sort input lines naturally\n"
"\n"
"SYNOPSIS\n"
" %s [OPTIONS]\n"
"\n"
"DESCRIPTION\n"
" Sort input lines case insensitively while comparing numbers (as in '[0-9]+')\n"
" by their value.\n"
" Only supports UTF-8 locales.\n"
"\n"
"OPTIONS\n"
" -h\n"
" Print this help message and exit.\n"
"\n"
" -v\n"
" Print the version and exit.\n"
"\n",
PROG_NAME, PROG_NAME);
exit(exit_status);
}
int main(int argc, char **argv)
{
signals_nointerrupt();
for (int opt; (opt = getopt(argc, argv, "hv")) != -1;)
{
switch (opt)
{
case 'h':
usage(EXIT_SUCCESS);
break;
case 'v':
puts(PROG_VERSION);
exit(EXIT_SUCCESS);
default:
exit(EXIT_FAILURE);
}
}
if (optind != argc)
{
usage(EXIT_FAILURE);
}
size_t dummy = 0;
char **lines, *line = NULL;
GARRAY_INIT(lines);
for (ssize_t read; (read = xgetline(&line, &dummy, stdin)) != -1;
dummy = 0, line = NULL)
{
GARRAY_APPEND(lines, line);
}
free(line);
qsort(lines, GARRAY_SIZE(lines), sizeof(char *), strnatcmp);
for (size_t i = 0; i < GARRAY_SIZE(lines); ++i)
{
puts(lines[i]);
}
PTR_GARRAY_FREE(lines, free);
return EXIT_SUCCESS;
}