/** 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 . */ #include "color_printer_mono.hpp" #include "color_printer_true.hpp" #include "basic_color_printer.tpp" #include "color_printer_base.hpp" #include "printer_base.tpp" #include "cmd.hpp" #include //fgetwc #include //wprintf, fgetwc, fileno #include //setlocale #include //setupterm, tigetnum, del_curterm #include //setupterm, tigetnum, del_curterm #include //isatty #include //strcmp #include //size_t #include //std::chrono::* #include //srand #include //char_traits #include //struct stat #include //struct stat constexpr const char version_string[] = "roflcat version 1.1"; static constexpr size_t constexpr_strlen(const char* str){ size_t i = 0; for(;*str;++str,++i){ if(*str == '\0'){ break; } } return i; } //Use ncurses (tinfo) to get the terminal's available color count static int detect_term_colors(void){ int term_colors = 16; if(setupterm(NULL, fileno(stdout), NULL) == ERR){ fprintf(stderr, "could not set up term\n"); }else{ term_colors = tigetnum("colors"); if(term_colors < 0){ term_colors = 16; //default to 16 color } del_curterm(cur_term); } return term_colors; } template constexpr void print_usage(Printer&& p){ p.print(L"Usage: roflcat [OPTION]... [FILE]...\n"); p.print(L"Concatenate FILE(s) to standard output with rainbows!\n"); p.print(L"\nWith no FILE, or when FILE is -, read standard input.\n\n"); for(size_t i = 0;i < cmd_arguments_list_size;++i){ const cmd_options& op = cmd_arguments_list[i]; size_t spacing = 6; p.print(L' ').print(L' '); if(op.sopt){ p.print(L'-').print(op.sopt); if(op.lopt) p.print(L',').print(L' '); }else{ p.print(L' ').print(L' ').print(L' ').print(L' '); } if(op.lopt){ spacing += constexpr_strlen(op.lopt)+4; p.print(L'-').print(L'-'); p.print(op.lopt); } for(size_t j = spacing;j < 30;++j){ p.print(L' '); } if(op.desc){ p.print(op.desc); } p.print(L'\n'); } p.print(L"\nroflcat Copyright (C) 2019 rexy712\n"); p.print(L"Source code can be found at https://gitlab.com/rexy712/roflcat\n"); p.print(L"This program comes with ABSOLUTELY NO WARRANTY\n"); p.print(L"This is free software, and you are welcome to redistribute it\n"); p.print(L"under certain conditions; see the GNU GPLv3 for details.\n"); p.print(L"A copy of the GPLv3 is available with the source in the file 'LICENSE'\n"); p.reset(); } template constexpr void print_version(Printer&& p){ p.print(version_string).print(L'\n'); p.reset(); } void to_caret_notation(int in, char* dest){ dest[0] = '^'; dest[1] = in ^ 0x40; dest[2] = 0; } void to_mdash_notation(int in, char* dest){ int corin = in & 0x7F; dest[0] = 'M'; dest[1] = '-'; if(corin > 32 && corin < 127){ dest[2] = corin; dest[3] = 0; }else{ char tmp[3]; to_caret_notation(corin, tmp); dest[2] = tmp[0]; dest[3] = tmp[1]; dest[4] = 0; } } void nonprinting_notation(int in, char* dest){ if(in & 0x80){ to_mdash_notation(in, dest); }else if(in == '\n' || in == '\t' || (in > 31 && in < 127)){ dest[0] = in; dest[1] = 0; }else{ to_caret_notation(in, dest); } } bool is_directory(const char* file){ struct stat st; if(stat(file, &st) != 0){ return false; } return S_ISDIR(st.st_mode); } bool file_exists(const char* file){ struct stat st; return stat(file, &st) == 0; } FILE* open_file(const char* file, const char* mode){ if(is_directory(file)){ fflush(stdout); fprintf(stderr, "Unable to open file \"%s\": Is a directory \n", file); return nullptr; } if(!file_exists(file)){ fflush(stdout); fprintf(stderr, "Unable to open file \"%s\": No such file\n", file); return nullptr; } FILE* fp = fopen(file, mode); if(!fp){ fflush(stdout); fprintf(stderr, "Unable to open file for reading: \"%s\"\n", file); } return fp; } template inline void do_stdin(Printer&& p, Func&& f){ for(T in;(in = f(stdin)) != std::char_traits::eof();){ p.print(in); if(in == '\n') p.reset(); } clearerr(stdin); } //Use the given type of printer to output the contents of files and/or stdin template int real_print_files(const cmd_args& args){ int ret = 0; Printer p(args); for(size_t i = 0;i < args.filenames.size();++i){ if(!strcmp(args.filenames[i], "-")){ //stdin if(args.treatbinary){ //Don't check if buf is empty because that's not how GNU cat handles Ctrl+D in middle of line for(int in;(in = fgetc(stdin)) != EOF;){ p.print(static_cast(in)); if(in == L'\n') //Only reset on newline to save print overhead p.reset(); } }else{ for(wint_t in;(in = fgetwc(stdin)) != WEOF;){ p.print(static_cast(in)); if(in == L'\n') p.reset(); } } clearerr(stdin); }else{ //everything besides stdin FILE* fp = open_file(args.filenames[i], "r"); if(!fp){ ret = 2; continue; } if(args.treatbinary){ if(args.nonprinting){ for(int in;(in = fgetc(fp)) != EOF;){ char tmp[5]; nonprinting_notation(in, tmp); p.print(tmp); } }else{ for(int in;(in = fgetc(fp)) != EOF;) p.print(static_cast(in)); } }else{ //set to wide character mode before anything fwide(fp, 1); for(wint_t in;(in = fgetwc(fp)) != WEOF;) p.print(static_cast(in)); } p.reset(); fclose(fp); } } p.reset(); return ret; } template int print_files(cmd_args& args){ if(args.usage){ args.clear_gnu_options(); print_usage(Printer{args}); return 0; }else if(args.version){ args.clear_gnu_options(); print_version(Printer{args}); return 0; } return real_print_files(args); } auto get_time(void){ auto time = std::chrono::high_resolution_clock::now(); auto eptime = time.time_since_epoch(); auto micros = std::chrono::duration_cast(eptime); return micros.count(); } int main(int argc, char** argv){ setlocale(LC_ALL, ""); //change to system locale so wchar_t things work cmd_args args = process_cmd_args(argc, argv); int avail_colors = detect_term_colors(); if(args.error){ fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]); return 1; } srand(args.seed ? args.seed : get_time()); //if stdout is not a tty, disable color 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){ return print_files>(args); }else if(avail_colors == 256){ return print_files>(args); } return 0; }