diff --git a/include/cmd.hpp b/include/cmd.hpp index 380689f..cae872f 100644 --- a/include/cmd.hpp +++ b/include/cmd.hpp @@ -36,18 +36,30 @@ extern const size_t cmd_arguments_list_size; #define PRINT_LINE_ALWAYS 1 #define PRINT_LINE_NONBLANK 2 +#define COLOR_DEFAULT 0 +#define COLOR_FORCE 1 +#define COLOR_DISABLE 2 //struct of command line options/values struct cmd_args{ std::vector filenames; + + //lolcat options float freq = 0.2f; float spread = 0.3f; long seed = 0; + unsigned truecol:1; + unsigned color:2; + + //GNU cat options unsigned number:2; unsigned ends:1; unsigned squeeze:1; unsigned tabs:1; unsigned nonprinting:1; + + //flags + unsigned error:1; unsigned usage:1; unsigned version:1; diff --git a/src/cmd.cpp b/src/cmd.cpp index 8de46cd..679b7d7 100644 --- a/src/cmd.cpp +++ b/src/cmd.cpp @@ -25,7 +25,7 @@ #include //atol cmd_args::cmd_args(void)noexcept: - number(0), ends(0), squeeze(0), tabs(0), nonprinting(0), usage(0), version(0){} + truecol(0), color(0), number(0), ends(0), squeeze(0), tabs(0), nonprinting(0), error(0), usage(0), version(0){} void cmd_args::clear_gnu_options(void){ number = PRINT_LINE_NEVER; ends = squeeze = tabs = nonprinting = 0; @@ -62,57 +62,89 @@ struct concat_many_strs{ }; #define NO_LONG_OPTION nullptr -constexpr const char SHOW_ALL_LONG_OPTION[] = "show-all"; -constexpr const char NUMBER_NONBLANK_LONG_OPTION[] = "number-nonblank"; -constexpr const char* VE_LONG_OPTION = NO_LONG_OPTION; -constexpr const char SHOW_ENDS_LONG_OPTION[] = "show-ends"; -constexpr const char NUMBER_LINES_LONG_OPTION[] = "number"; -constexpr const char SQUEEZE_BLANKS_LONG_OPTION[] = "squeeze-blank"; -constexpr const char* VT_LONG_OPTION = NO_LONG_OPTION; -constexpr const char SHOW_TABS_LONG_OPTION[] = "show-tabs"; -constexpr const char* U_IGNORED_LONG_OPTION = NO_LONG_OPTION; -constexpr const char NONPRINTING_LONG_OPTION[] = "show-nonprinting"; -constexpr const char USAGE_LONG_OPTION[] = "help"; -constexpr const char VERSION_LONG_OPTION[] = "version"; -constexpr const char SEED_LONG_OPTION[] = "seed="; +#define REQ_INTEGER_ARG "=" +#define REQ_FLOAT_ARG "=" +static constexpr const char SHOW_ALL_LONG_OPTION[] = "show-all"; +static constexpr const char NUMBER_NONBLANK_LONG_OPTION[] = "number-nonblank"; +static constexpr const char* VE_LONG_OPTION = NO_LONG_OPTION; +static constexpr const char SHOW_ENDS_LONG_OPTION[] = "show-ends"; +static constexpr const char NUMBER_LINES_LONG_OPTION[] = "number"; +static constexpr const char SQUEEZE_BLANKS_LONG_OPTION[] = "squeeze-blank"; +static constexpr const char* VT_LONG_OPTION = NO_LONG_OPTION; +static constexpr const char SHOW_TABS_LONG_OPTION[] = "show-tabs"; +static constexpr const char* U_IGNORED_LONG_OPTION = NO_LONG_OPTION; +static constexpr const char NONPRINTING_LONG_OPTION[] = "show-nonprinting"; +static constexpr const char USAGE_LONG_OPTION[] = "help"; +static constexpr const char VERSION_LONG_OPTION[] = "version"; +static constexpr const char SEED_LONG_OPTION[] = "seed" REQ_INTEGER_ARG; +static constexpr const char SPREAD_LONG_OPTION[] = "spread" REQ_FLOAT_ARG; +static constexpr const char FREQ_LONG_OPTION[] = "freq" REQ_FLOAT_ARG; +static constexpr const char TRUECOLOR_LONG_OPTION[] = "truecolor"; +static constexpr const char FORCE_COLOR_LONG_OPTION[] = "force"; +static constexpr const char DISABLE_COLOR_LONG_OPTION[] = "no-color"; + +static constexpr const char IGNORE_ANIMATE_LONG_OPTION[] = "animate"; +static constexpr const char IGNORE_DURATION_LONG_OPTION[] = "duration" REQ_INTEGER_ARG; +static constexpr const char IGNORE_SPEED_LONG_OPTION[] = "speed" REQ_FLOAT_ARG; #define NO_SHORT_OPTION "" -constexpr const char SHOW_ALL_SHORT_OPTION[] = "A"; -constexpr const char NUMBER_NONBLANK_SHORT_OPTION[] = "b"; -constexpr const char VE_SHORT_OPTION[] = "e"; -constexpr const char SHOW_ENDS_SHORT_OPTION[] = "E"; -constexpr const char NUMBER_LINES_SHORT_OPTION[] = "n"; -constexpr const char SQUEEZE_BLANKS_SHORT_OPTION[] = "s"; -constexpr const char VT_SHORT_OPTION[] = "t"; -constexpr const char SHOW_TABS_SHORT_OPTION[] = "T"; -constexpr const char U_IGNORED_SHORT_OPTION[] = "u"; -constexpr const char NONPRINTING_SHORT_OPTION[] = "v"; -constexpr const char USAGE_SHORT_OPTION[] = "h"; -constexpr const char* VERSION_SHORT_OPTION = NO_SHORT_OPTION; -constexpr const char SEED_SHORT_OPTION[] = "S"; +static constexpr const char SHOW_ALL_SHORT_OPTION[] = "A"; +static constexpr const char NUMBER_NONBLANK_SHORT_OPTION[] = "b"; +static constexpr const char TRUECOLOR_SHORT_OPTION[] = "C"; +static constexpr const char VE_SHORT_OPTION[] = "e"; +static constexpr const char SHOW_ENDS_SHORT_OPTION[] = "E"; +static constexpr const char FORCE_COLOR_SHORT_OPTION[] = "f"; +static constexpr const char FREQ_SHORT_OPTION[] = "F"; +static constexpr const char USAGE_SHORT_OPTION[] = "h"; +static constexpr const char NUMBER_LINES_SHORT_OPTION[] = "n"; +static constexpr const char DISABLE_COLOR_SHORT_OPTION[] = "N"; +static constexpr const char SPREAD_SHORT_OPTION[] = "p"; +static constexpr const char SQUEEZE_BLANKS_SHORT_OPTION[] = "s"; +static constexpr const char SEED_SHORT_OPTION[] = "S"; +static constexpr const char VT_SHORT_OPTION[] = "t"; +static constexpr const char SHOW_TABS_SHORT_OPTION[] = "T"; +static constexpr const char U_IGNORED_SHORT_OPTION[] = "u"; +static constexpr const char NONPRINTING_SHORT_OPTION[] = "v"; + +static constexpr const char IGNORE_ANIMATE_SHORT_OPTION[] = "a"; +static constexpr const char IGNORE_DURATION_SHORT_OPTION[] = "d"; +static constexpr const char* IGNORE_SPEED_SHORT_OPTION = NO_SHORT_OPTION; + +static constexpr const char* VERSION_SHORT_OPTION = NO_SHORT_OPTION; #define NO_DESC nullptr -constexpr const char EQUIV_DESC_BASE[] = "equivalent to -"; -constexpr const char* SHOW_ALL_DESC = concat_many_strs::str; -constexpr const char NUMBER_NONBLANK_DESC_BASE[] = "number nonempty output lines, overrides -"; -constexpr const char* NUMBER_NONBLANK_DESC = concat_many_strs::str; -constexpr const char* VE_DESC = concat_many_strs::str; -constexpr const char SHOW_ENDS_DESC[] = "display $ at end of each line"; -constexpr const char NUMBER_LINES_DESC[] = "number all output lines"; -constexpr const char SQUEEZE_BLANKS_DESC[] = "suppress repeated empty output lines"; -constexpr const char* VT_DESC = concat_many_strs::str; -constexpr const char SHOW_TABS_DESC[] = "desplay TAB characters as ^I"; -constexpr const char U_IGNORED_DESC[] = "(ignored)"; -constexpr const char NONPRINTING_DESC[] = "should use ^ and M- notation, except for LFD and TAB (not supported as of now)"; -constexpr const char USAGE_DESC[] = "display this help and exit"; -constexpr const char VERSION_DESC[] = "output version information and exit"; -constexpr const char SEED_DESC[] = "Rainbow seed, 0 = random (default: 0)"; +static constexpr const char IGNORED_DESC[] = "Ignored for compatability"; +static constexpr const char EQUIV_DESC_BASE[] = "equivalent to -"; +static constexpr const char* SHOW_ALL_DESC = concat_many_strs::str; +static constexpr const char NUMBER_NONBLANK_DESC_BASE[] = "number nonempty output lines, overrides -"; +static constexpr const char* NUMBER_NONBLANK_DESC = concat_many_strs::str; +static constexpr const char* VE_DESC = concat_many_strs::str; +static constexpr const char SHOW_ENDS_DESC[] = "display $ at end of each line"; +static constexpr const char NUMBER_LINES_DESC[] = "number all output lines"; +static constexpr const char SQUEEZE_BLANKS_DESC[] = "suppress repeated empty output lines"; +static constexpr const char* VT_DESC = concat_many_strs::str; +static constexpr const char SHOW_TABS_DESC[] = "display TAB characters as ^I"; +static constexpr const char* U_IGNORED_DESC = IGNORED_DESC; +static constexpr const char NONPRINTING_DESC[] = "should use ^ and M- notation, except for LFD and TAB (not supported as of now)"; +static constexpr const char USAGE_DESC[] = "display this help and exit"; +static constexpr const char VERSION_DESC[] = "output version information and exit"; +static constexpr const char SEED_DESC[] = "Rainbow seed, 0 = random (default: 0)"; +static constexpr const char SPREAD_DESC[] = "Rainbow spread (default: Not yet decided)"; +static constexpr const char FREQ_DESC[] = "Rainbow frequency (default: Not yet decided)"; +static constexpr const char TRUECOLOR_DESC[] = "24-bit (truecolor)"; +static constexpr const char FORCE_COLOR_DESC[] = "Force color even when stdout is not a tty"; +static constexpr const char DISABLE_COLOR_DESC_BASE[] = "Suppress color even when stdout is a tty, overrides -"; +static constexpr const char* DISABLE_COLOR_DESC = concat_many_strs::str; + +static constexpr const char* IGNORE_ANIMATE_DESC = IGNORED_DESC; +static constexpr const char* IGNORE_DURATION_DESC = IGNORED_DESC; +static constexpr const char* IGNORE_SPEED_DESC = IGNORED_DESC; #define SHORT_OPT(x) (x##_SHORT_OPTION)[0] #define OPTION(x) {x##_LONG_OPTION, SHORT_OPT(x), x##_DESC} -constexpr bool is_integer(const char* s){ +static constexpr bool is_integer(const char* s){ if(*s != '-' && (*s < '0' || *s > '9')){ return false; } @@ -121,16 +153,63 @@ constexpr bool is_integer(const char* s){ return false; } } + if(*(s-1) == '-') + return false; + return true; +} +static constexpr bool is_float(const char* s){ + if(*s != '-' && (*s < '0' || *s > '9')){ + return false; + } + if(*s == '-' && s[1] == 0){ + return false; + } + bool has_decimal = false; + for(s = s+1;*s;++s){ + if(*s < '0' || *s > '9'){ + if(*s != '.' || has_decimal){ + return false; + }else{ + has_decimal = true; + } + } + } + if(*(s-1) == '.' || *(s-1) == '-') + return false; return true; } -constexpr size_t strlen_pre_eq(const char* str){ +static constexpr size_t strlen_pre_eq(const char* str){ size_t i = 0; for(;str[i] && str[i] != '=';++i); return i; } -#define CHECK_LONG_OPTION(x, arg) (!strncmp(x##_LONG_OPTION, arg, strlen_pre_eq(x##_LONG_OPTION))) +#define CHECK_VALID_LONG_ARG(opt, off, arg, type) \ + { \ + if(arg[off] != '=' || !is_##type(arg+off+1)){ \ + fwprintf(stderr, L"'--%.*s' requires an argument of type " #type "\n", off, opt); \ + ret.error = 1; \ + break; \ + } \ + } +#define CHECK_VALID_LONG_FLOAT_ARG(opt, off, arg) CHECK_VALID_LONG_ARG(opt##_LONG_OPTION, off, arg, float) +#define CHECK_VALID_LONG_INT_ARG(opt, off, arg) CHECK_VALID_LONG_ARG(opt##_LONG_OPTION, off, arg, integer) + +#define CHECK_VALID_SHORT_ARG(opt, arg, type) \ + { \ + if(next_arg == (argc-1) || !is_##type(arg)){ \ + fwprintf(stderr, L"'-%s' requires an argument of type " #type "\n", opt); \ + ret.error = 1; \ + break; \ + } \ + } +#define CHECK_VALID_SHORT_INT_ARG(opt, arg) CHECK_VALID_SHORT_ARG(opt##_SHORT_OPTION, arg, integer) +#define CHECK_VALID_SHORT_FLOAT_ARG(opt, arg) CHECK_VALID_SHORT_ARG(opt##_SHORT_OPTION, arg, float) + + +#define CHECK_LONG_OPTION(x, arg) (!strcmp(x##_LONG_OPTION, arg)) +#define CHECK_LONG_OPTION_WITH_ARG(x, arg) (!strncmp(x##_LONG_OPTION, arg, strlen_pre_eq(x##_LONG_OPTION))) #define IS_SHORT_OPTION(arg) ((arg)[0] == '-' && (arg)[1] != '-') #define IS_LONG_OPTION(arg) ((arg)[0] == '-' && (arg)[1] == '-') //Convert command line options to a cmd_args struct @@ -146,7 +225,7 @@ cmd_args process_cmd_args(int argc, char** argv){ escaped = true; }else if(IS_SHORT_OPTION(argv[i])){ size_t arg_len = strlen(argv[i]); - size_t next_arg = i+1; + int next_arg = i+1; for(size_t j = 1;j < arg_len;++j){ switch(argv[i][j]){ case SHORT_OPT(SHOW_ALL): @@ -185,18 +264,41 @@ cmd_args process_cmd_args(int argc, char** argv){ case SHORT_OPT(VERSION): ret.version = 1; break; + case SHORT_OPT(TRUECOLOR): + ret.truecol = 1; + break; + case SHORT_OPT(DISABLE_COLOR): + ret.color = COLOR_DISABLE; + break; + case SHORT_OPT(FORCE_COLOR): + if(ret.color == COLOR_DEFAULT) + ret.color = COLOR_FORCE; + break; case SHORT_OPT(SEED): - if(i == (argc-1) || !is_integer(argv[next_arg])){ - fwprintf(stderr, L"'-%s' requires an integer argument\n", SEED_SHORT_OPTION); - ret.usage = 1; - }else{ - ret.seed = atol(argv[next_arg]); - } + CHECK_VALID_SHORT_INT_ARG(SEED, argv[next_arg]); + ret.seed = atol(argv[next_arg]); ++next_arg; break; + case SHORT_OPT(SPREAD): + CHECK_VALID_SHORT_FLOAT_ARG(SPREAD, argv[next_arg]); + ret.spread = atof(argv[next_arg]); + ++next_arg; + break; + case SHORT_OPT(FREQ): + CHECK_VALID_SHORT_FLOAT_ARG(FREQ, argv[next_arg]); + ret.freq = atof(argv[next_arg]); + ++next_arg; + break; + case SHORT_OPT(IGNORE_ANIMATE): + break; + case SHORT_OPT(IGNORE_DURATION): + if(next_arg != (argc-1)){ + ++next_arg; + } + break; default: //keep parsing after error so we can still properly output requested color style - ret.usage = 1; + ret.error = 1; fwprintf(stderr, L"Unrecognized option '-%c'\n", argv[i][j]); }; } @@ -220,16 +322,32 @@ cmd_args process_cmd_args(int argc, char** argv){ ret.usage = 1; }else if(CHECK_LONG_OPTION(VERSION, arg)){ ret.version = 1; - }else if(CHECK_LONG_OPTION(SEED, arg)){ + }else if(CHECK_LONG_OPTION(TRUECOLOR, arg)){ + ret.truecol = 1; + }else if(CHECK_LONG_OPTION(DISABLE_COLOR, arg)){ + ret.color = COLOR_DISABLE; + }else if(CHECK_LONG_OPTION(FORCE_COLOR, arg)){ + if(ret.color == COLOR_DEFAULT) + ret.color = COLOR_FORCE; + }else if(CHECK_LONG_OPTION_WITH_ARG(SEED, arg)){ constexpr size_t offset = strlen_pre_eq(SEED_LONG_OPTION); - if(arg[offset] != '=' || !is_integer(arg+offset+1)){ - fwprintf(stderr, L"'--%.*s' requires an integer argument\n", offset, SEED_LONG_OPTION); - ret.usage = 1; - }else{ - ret.seed = atol(arg+offset+1); - } + CHECK_VALID_LONG_INT_ARG(SEED, offset, arg); + ret.seed = atol(arg+offset+1); + }else if(CHECK_LONG_OPTION_WITH_ARG(SPREAD, arg)){ + constexpr size_t offset = strlen_pre_eq(SPREAD_LONG_OPTION); + CHECK_VALID_LONG_FLOAT_ARG(SPREAD, offset, arg); + ret.spread = atof(arg+offset+1); + }else if(CHECK_LONG_OPTION_WITH_ARG(FREQ, arg)){ + constexpr size_t offset = strlen_pre_eq(FREQ_LONG_OPTION); + CHECK_VALID_LONG_FLOAT_ARG(FREQ, offset, arg); + ret.freq = atof(arg+offset+1); + }else if(CHECK_LONG_OPTION(IGNORE_ANIMATE, arg) || + CHECK_LONG_OPTION_WITH_ARG(IGNORE_DURATION, arg) || + CHECK_LONG_OPTION_WITH_ARG(IGNORE_SPEED, arg)) + { + ; }else{ - ret.usage = 1; + ret.error = 1; fwprintf(stderr, L"Unrecognized option '%s'\n", argv[i]); } }else{ @@ -253,7 +371,17 @@ const cmd_options cmd_arguments_list[] = { OPTION(SHOW_TABS), OPTION(U_IGNORED), OPTION(NONPRINTING), + {nullptr,0,nullptr}, //add newline to output OPTION(SEED), + OPTION(SPREAD), + OPTION(FREQ), + OPTION(TRUECOLOR), + OPTION(FORCE_COLOR), + OPTION(DISABLE_COLOR), + OPTION(IGNORE_ANIMATE), + OPTION(IGNORE_DURATION), + OPTION(IGNORE_SPEED), + {nullptr,0,nullptr}, OPTION(USAGE), OPTION(VERSION) }; diff --git a/src/roflcat.cpp b/src/roflcat.cpp index 9126f6c..eef15ec 100644 --- a/src/roflcat.cpp +++ b/src/roflcat.cpp @@ -105,16 +105,17 @@ constexpr void print_version(Printer&& p){ //Use the given type of printer to output the contents of files and/or stdin template -void print_files(cmd_args& args){ +int print_files(cmd_args& args){ if(args.usage){ args.clear_gnu_options(); print_usage(Printer{args}); - return; + return 0; }else if(args.version){ args.clear_gnu_options(); print_version(Printer{args}); - return; + return 0; } + int ret = 0; Printer p(args); for(size_t i = 0;i < args.filenames.size();++i){ if(!strcmp(args.filenames[i], "-")){ //stdin @@ -130,6 +131,7 @@ void print_files(cmd_args& args){ if(!fp){ fflush(stdout); //force color update before error fwprintf(stderr, L"Unable to open file %s\n", args.filenames[i]); + ret = 2; continue; } //set to wide character mode before anything @@ -141,6 +143,7 @@ void print_files(cmd_args& args){ } } p.reset(); + return ret; } auto get_time(void){ auto time = std::chrono::high_resolution_clock::now(); @@ -154,15 +157,21 @@ int main(int argc, char** argv){ cmd_args args = process_cmd_args(argc, argv); int avail_colors = detect_term_colors(); + if(args.error){ + fwprintf(stderr, L"Try '%s --help' for more information.\n", argv[0]); + return 1; + } + srand(args.seed ? args.seed : get_time()); - //print_files(args); //if stdout is not a tty, disable color - if(!isatty(fileno(stdout))){ - print_files(args); + if(args.color == COLOR_DISABLE || (!isatty(fileno(stdout)) && args.color != COLOR_FORCE)){ + return print_files(args); + }else if(args.truecol){ + return print_files(args); }else if(avail_colors == 8 || avail_colors == 16){ - print_files>(args); + return print_files>(args); }else if(avail_colors == 256){ - print_files>(args); + return print_files>(args); } return 0;