roflcat/src/cmd.cpp
2019-01-15 11:39:57 -08:00

399 lines
15 KiB
C++

/**
roflcat
Copyright (C) 2019 rexy712
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cmd.hpp"
#include <cstddef> //size_t
#include <cstring> //strncmp
#include <cwchar>
#include <cstdio> //fwprintf, stderr
#include <cstdlib> //atol
cmd_args::cmd_args(void)noexcept:
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;
}
static constexpr size_t constexpr_strlen(const char* str){
size_t i = 0;
for(;*str;++str,++i){
if(*str == '\0'){
break;
}
}
return i;
}
template<const char* S1, const char* S2, size_t I = 0, size_t J = 0, size_t Size1 = constexpr_strlen(S1), size_t Size2 = constexpr_strlen(S2), char... Cs>
struct concat_strs{
static constexpr const char* str = concat_strs<S1,S2,I+1,J,Size1-1,Size2,Cs...,S1[I]>::str;
};
template<const char* S1, const char* S2, size_t I, size_t J, size_t Size2, char... Cs>
struct concat_strs<S1,S2,I,J,0,Size2,Cs...>{
static constexpr const char* str = concat_strs<S1,S2,I,J+1,0,Size2-1,Cs...,S2[J]>::str;
};
template<const char* S1, const char* S2, size_t I, size_t J, char... Cs>
struct concat_strs<S1,S2,I,J,0,0,Cs...>{
static constexpr const char str[sizeof...(Cs)+1] = {Cs..., '\0'};
};
template<const char* S1, const char* S2, const char*... Strs>
struct concat_many_strs{
static constexpr const char* str = concat_many_strs<concat_strs<S1,S2>::str, Strs...>::str;
};
template<const char* S1, const char* S2>
struct concat_many_strs<S1,S2>{
static constexpr const char* str = concat_strs<S1, S2>::str;
};
#define NO_LONG_OPTION nullptr
#define REQ_INTEGER_ARG "=<i>"
#define REQ_FLOAT_ARG "=<f>"
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 INVERT_LONG_OPTION[] = "invert";
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 ""
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 INVERT_SHORT_OPTION[] = "i";
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
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<EQUIV_DESC_BASE,NONPRINTING_SHORT_OPTION,SHOW_ENDS_SHORT_OPTION,SHOW_TABS_SHORT_OPTION>::str;
static constexpr const char NUMBER_NONBLANK_DESC_BASE[] = "number nonempty output lines, overrides -";
static constexpr const char* NUMBER_NONBLANK_DESC = concat_many_strs<NUMBER_NONBLANK_DESC_BASE,SHOW_TABS_SHORT_OPTION>::str;
static constexpr const char* VE_DESC = concat_many_strs<EQUIV_DESC_BASE,NONPRINTING_SHORT_OPTION,SHOW_ENDS_SHORT_OPTION>::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<EQUIV_DESC_BASE,NONPRINTING_SHORT_OPTION,SHOW_TABS_SHORT_OPTION>::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<DISABLE_COLOR_DESC_BASE,FORCE_COLOR_SHORT_OPTION>::str;
static constexpr const char INVERT_DESC[] = "Change background color instead of foreground";
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}
static constexpr bool is_integer(const char* s){
if(*s != '-' && (*s < '0' || *s > '9')){
return false;
}
for(s = s+1;*s;++s){
if(*s < '0' || *s > '9'){
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;
}
static constexpr size_t strlen_pre_eq(const char* str){
size_t i = 0;
for(;str[i] && str[i] != '=';++i);
return i;
}
#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 || !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
cmd_args process_cmd_args(int argc, char** argv){
cmd_args ret;
bool escaped = false;
for(int i = 1;i < argc;++i){
if(escaped){
ret.filenames.push_back(argv[i]);
continue;
}
if(!strcmp(argv[i], "--")){
escaped = true;
}else if(IS_SHORT_OPTION(argv[i])){
size_t arg_len = strlen(argv[i]);
int next_arg = i+1;
for(size_t j = 1;j < arg_len;++j){
switch(argv[i][j]){
case SHORT_OPT(SHOW_ALL):
ret.nonprinting = ret.ends = ret.tabs = 1;
break;
case SHORT_OPT(NUMBER_NONBLANK):
ret.number = PRINT_LINE_NONBLANK;
break;
case SHORT_OPT(VE):
ret.nonprinting = ret.ends = 1;
break;
case SHORT_OPT(SHOW_ENDS):
ret.ends = 1;
break;
case SHORT_OPT(NUMBER_LINES):
if(!ret.number)
ret.number = PRINT_LINE_ALWAYS;
break;
case SHORT_OPT(SQUEEZE_BLANKS):
ret.squeeze = 1;
break;
case SHORT_OPT(VT):
ret.nonprinting = ret.tabs = 1;
break;
case SHORT_OPT(SHOW_TABS):
ret.tabs = 1;
break;
case SHORT_OPT(U_IGNORED):
break;
case SHORT_OPT(NONPRINTING):
ret.nonprinting = 1;
break;
case SHORT_OPT(USAGE):
ret.usage = 1;
break;
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(INVERT):
ret.invert = 1;
break;
case SHORT_OPT(SEED):
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.error = 1;
fwprintf(stderr, L"Unrecognized option '-%c'\n", argv[i][j]);
};
}
i = next_arg-1;
}else if(IS_LONG_OPTION(argv[i])){
const char* arg = argv[i]+2;
if(CHECK_LONG_OPTION(SHOW_ENDS, arg)){
ret.ends = 1;
}else if(CHECK_LONG_OPTION(NUMBER_NONBLANK, arg)){
ret.number = PRINT_LINE_NONBLANK;
}else if(CHECK_LONG_OPTION(NUMBER_LINES, arg)){
if(!ret.number)
ret.number = PRINT_LINE_ALWAYS;
}else if(CHECK_LONG_OPTION(SQUEEZE_BLANKS, arg)){
ret.squeeze = 1;
}else if(CHECK_LONG_OPTION(SHOW_TABS, arg)){
ret.tabs = 1;
}else if(CHECK_LONG_OPTION(NONPRINTING, arg)){
ret.nonprinting = 1;
}else if(CHECK_LONG_OPTION(USAGE, arg)){
ret.usage = 1;
}else if(CHECK_LONG_OPTION(VERSION, arg)){
ret.version = 1;
}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(INVERT, arg)){
ret.invert = 1;
}else if(CHECK_LONG_OPTION_WITH_ARG(SEED, arg)){
constexpr size_t offset = strlen_pre_eq(SEED_LONG_OPTION);
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.error = 1;
fwprintf(stderr, L"Unrecognized option '%s'\n", argv[i]);
}
}else{
ret.filenames.push_back(argv[i]);
}
}
if(ret.filenames.size() == 0){
ret.filenames.push_back("-");
}
return ret;
}
const cmd_options cmd_arguments_list[] = {
OPTION(SHOW_ALL),
OPTION(NUMBER_NONBLANK),
OPTION(VE),
OPTION(SHOW_ENDS),
OPTION(NUMBER_LINES),
OPTION(SQUEEZE_BLANKS),
OPTION(VT),
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),
OPTION(INVERT),
{nullptr,0,nullptr},
OPTION(USAGE),
OPTION(VERSION)
};
const size_t cmd_arguments_list_size = sizeof(cmd_arguments_list)/sizeof(cmd_arguments_list[0]);