Skip to content

Commit

Permalink
feat(cli): make parser less ad hoc
Browse files Browse the repository at this point in the history
Handle `info` and `version` like the main commands.
Disallow options with `decode`.
Do not print the message "too few arguments" when the real error is an
invalid command.
  • Loading branch information
dbohdan committed Sep 29, 2023
1 parent c05766a commit ebf854b
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 60 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,20 @@ HiColor has a Git-style CLI.
The actions `encode` and `decode` convert images between PNG and HiColor's own image format. `quantize` round-trips an image through the converter and outputs a normal PNG. Use it to create images that look high-color but aren't. `info` displays information about a HiColor file: version (`5` for 15-bit or `6` for 16), width, and height.

```none
HiColor
HiColor 0.4.0
Create 15/16-bit color RGB images.
usage:
hicolor (encode|decode|quantize) [options] src [dest]
hicolor info file
hicolor version
hicolor (help|-h|--help)
hicolor (encode|quantize) [-5|-6] [-n] [--] <src> [<dest>]
hicolor (decode <src> [<dest>]|info <file>|version|help|-h|--help)
commands:
encode convert PNG to HiColor
decode convert HiColor to PNG
quantize quantize PNG to PNG
info print file version and resolution
version print program version
help print this help message
options:
-5, --15-bit 15-bit color
Expand Down
116 changes: 64 additions & 52 deletions cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,8 @@ void usage(FILE* output)
fprintf(
output,
"usage:\n"
" hicolor (encode|decode|quantize) [-5|-6] [-n] <src> [<dest>]\n"
" hicolor (info <file>|version|help|-h|--help)\n"
" hicolor (encode|quantize) [-5|-6] [-n] [--] <src> [<dest>]\n"
" hicolor (decode <src> [<dest>]|info <file>|version|help|-h|--help)\n"
);
}

Expand Down Expand Up @@ -381,107 +381,119 @@ bool str_prefix(const char* ref, const char* str)
}

typedef enum command {
ENCODE, DECODE, QUANTIZE
ENCODE, DECODE, QUANTIZE, INFO, VERSION
} command;

int main(int argc, char** argv)
{
command opt_command = ENCODE;
bool opt_dither = true;
hicolor_version opt_version = HICOLOR_VERSION_6;
char* opt_src;
char* opt_dest;
char* arg_src;
char* arg_dest;
bool allow_opts = true;
int min_pos_args = 1;
int max_pos_args = 2;

if (argc == 2 && str_prefix("version", argv[1])) {
version();
return 0;
if (argc <= 1) {
help();
return 1;
}

if (argc == 2
&& (str_prefix("help", argv[1])
|| strcmp(argv[1], "-h") == 0
|| strcmp(argv[1], "--help") == 0)) {
if (str_prefix("help", argv[1])
|| strcmp(argv[1], "-h") == 0
|| strcmp(argv[1], "--help") == 0) {
help();
return 0;
}

if (argc == 3 && str_prefix("info", argv[1])) {
return !hicolor_print_info(argv[2]);
}

if (argc < 3) {
fprintf(stderr, HICOLOR_CLI_ERROR "too few arguments\n");
usage(stderr);
return 1;
}

int i = 1;

if (str_prefix("encode", argv[i])) {
opt_command = ENCODE;
} else if (str_prefix("decode", argv[i])) {
allow_opts = false;
opt_command = DECODE;
} else if (str_prefix("quantize", argv[i])) {
opt_command = QUANTIZE;
} else if (str_prefix("info", argv[i])) {
allow_opts = false;
max_pos_args = 1;
opt_command = INFO;
} else if (str_prefix("version", argv[i])) {
allow_opts = false;
min_pos_args = 0;
max_pos_args = 0;
opt_command = VERSION;
} else {
fprintf(stderr, HICOLOR_CLI_ERROR "invalid command\n");
usage(stderr);
return 1;
}

i++;

while (i < argc && argv[i][0] == '-') {
if (strcmp(argv[i], "--") == 0) {
if (allow_opts) {
while (i < argc && argv[i][0] == '-') {
if (strcmp(argv[i], "--") == 0) {
i++;
break;
} else if (strcmp(argv[i], "-5") == 0
|| strcmp(argv[i], "--15-bit") == 0) {
opt_version = HICOLOR_VERSION_5;
} else if (strcmp(argv[i], "-6") == 0
|| strcmp(argv[i], "--16-bit") == 0) {
opt_version = HICOLOR_VERSION_6;
} else if (strcmp(argv[i], "-n") == 0
|| strcmp(argv[i], "--no-dither") == 0) {
opt_dither = false;
}

i++;
break;
} else if (strcmp(argv[i], "-5") == 0
|| strcmp(argv[i], "--15-bit") == 0) {
opt_version = HICOLOR_VERSION_5;
} else if (strcmp(argv[i], "-6") == 0
|| strcmp(argv[i], "--16-bit") == 0) {
opt_version = HICOLOR_VERSION_6;
} else if (strcmp(argv[i], "-n") == 0
|| strcmp(argv[i], "--no-dither") == 0) {
opt_dither = false;
}

i++;
}

if (i >= argc) {
int rem_args = argc - i;

if (rem_args < min_pos_args) {
fprintf(stderr, HICOLOR_CLI_ERROR "too few arguments\n");
usage(stderr);
return 1;
}

opt_src = argv[i];
if (rem_args > max_pos_args) {
fprintf(stderr, HICOLOR_CLI_ERROR "too many arguments\n");
usage(stderr);
return 1;
}

arg_src = argv[i];
i++;

if (i == argc) {
opt_dest = malloc(strlen(opt_src) + 5);
if (opt_dest == NULL) return HICOLOR_CLI_NO_MEMORY_EXIT_CODE;
arg_dest = malloc(strlen(arg_src) + 5);
if (arg_dest == NULL) return HICOLOR_CLI_NO_MEMORY_EXIT_CODE;
sprintf(
opt_dest,
arg_dest,
opt_command == ENCODE ? "%s.hic" : "%s.png",
opt_src
arg_src
);
} else {
opt_dest = argv[i];
arg_dest = argv[i];
}
i++;

if (i < argc) {
fprintf(stderr, HICOLOR_CLI_ERROR "too many arguments\n");
usage(stderr);
return 1;
}

switch (opt_command) {
case ENCODE:
return !png_to_hicolor(opt_version, opt_dither, opt_src, opt_dest);
return !png_to_hicolor(opt_version, opt_dither, arg_src, arg_dest);
case DECODE:
return !hicolor_to_png(opt_src, opt_dest);
return !hicolor_to_png(arg_src, arg_dest);
case QUANTIZE:
return !png_quantize(opt_version, opt_dither, opt_src, opt_dest);
return !png_quantize(opt_version, opt_dither, arg_src, arg_dest);
case INFO:
return !hicolor_print_info(arg_src);
case VERSION:
version();
return 0;
}
}
39 changes: 36 additions & 3 deletions tests/hicolor.test
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ tcltest::test version-1.1 {} -body {
hicolor version
} -match regexp -result {\d+\.\d+\.\d+}

tcltest::test version-1.2 {} -body {
hicolor version file.hi5
} -returnCodes error -match glob -result {*too many arg*}

tcltest::test version-2.1 {} -body {
set action version
set output [hicolor $action]
Expand All @@ -57,17 +61,17 @@ tcltest::test version-2.1 {} -body {

tcltest::test help-1.1 {} -body {
hicolor help
} -match glob -result *usage:*
} -match glob -result *options:*


tcltest::test help-1.2 {} -body {
hicolor -h
} -match glob -result *usage:*
} -match glob -result *options:*


tcltest::test help-1.3 {} -body {
hicolor --help
} -match glob -result *usage:*
} -match glob -result *options:*


tcltest::test encode-1.1 {} -body {
Expand Down Expand Up @@ -129,6 +133,11 @@ tcltest::test encode-2.7 {encode flags} -body {
hicolor encode -n -n -n -n -n photo.png
} -result {}

tcltest::test encode-2.8 {encode flags} -body {
hicolor encode -6 -n -5 photo.png
hicolor info photo.png.hic
} -result {5 640 427}


tcltest::test encode-3.1 {bad input} -body {
hicolor encode truncated.png
Expand Down Expand Up @@ -157,6 +166,10 @@ tcltest::test decode-1.3 {bad input} -body {
hicolor decode [file tail [info script]]
} -returnCodes error -match glob -result {*bad magic*}

tcltest::test decode-1.4 {bad input} -body {
hicolor decode -5 photo.hi5
} -returnCodes error -match glob -result *error:*


tcltest::test quantize-1.1 {} -body {
hicolor quantize photo.png photo.16-bit.png
Expand All @@ -181,6 +194,26 @@ tcltest::test quantize-2.3 {bad input} -body {
} -returnCodes error -match glob -result {error: can't load PNG file*}


tcltest::test invalid-command-1.1 {} -body {
hicolor -5 src.png
} -returnCodes error -match glob -result {error: invalid command*}

tcltest::test invalid-command-1.2 {} -body {
hicolor encoder
} -returnCodes error -match glob -result {error: invalid command*}


tcltest::test no-arguments-1.1 {} -body {
hicolor
} -returnCodes error -match glob -result *options:*


tcltest::test dash-dash-1.1 {} -body {
hicolor encode -- --no-such-file
} -returnCodes error -match glob -result {error: source image "--no-such-file"\
doesn't exist}


tcltest::test data-integrity-1.1 {roundtrip} -constraints gm -body {
hicolor decode photo.hi5 temp.png
exec gm compare -metric rmse photo.png temp.png
Expand Down

0 comments on commit ebf854b

Please sign in to comment.