roflcat/src/roflcat.cpp

281 lines
7.3 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 "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 <cwchar> //fgetwc
#include <cstdio> //wprintf, fgetwc, fileno
#include <locale.h> //setlocale
#include <curses.h> //setupterm, tigetnum, del_curterm
#include <term.h> //setupterm, tigetnum, del_curterm
#include <unistd.h> //isatty
#include <cstring> //strcmp
#include <cstddef> //size_t
#include <chrono> //std::chrono::*
#include <cstdlib> //srand
#include <string> //char_traits
#include <sys/types.h> //struct stat
#include <sys/stat.h> //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<class Printer>
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<class Printer>
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<class T, class Printer, class Func>
inline void do_stdin(Printer&& p, Func&& f){
for(T in;(in = f(stdin)) != std::char_traits<T>::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<class Printer>
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<char>(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<wchar_t>(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<char>(in));
}
}else{
//set to wide character mode before anything
fwide(fp, 1);
for(wint_t in;(in = fgetwc(fp)) != WEOF;)
p.print(static_cast<wchar_t>(in));
}
p.reset();
fclose(fp);
}
}
p.reset();
return ret;
}
template<class Printer>
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<Printer>(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<std::chrono::microseconds>(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<color_printer_mono>(args);
}else if(args.truecol){
return print_files<color_printer_true>(args);
}else if(avail_colors == 8 || avail_colors == 16){
return print_files<basic_color_printer<16>>(args);
}else if(avail_colors == 256){
return print_files<basic_color_printer<256>>(args);
}
return 0;
}