281 lines
7.3 KiB
C++
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;
|
|
}
|