%% options copyright owner = Dirk Krause copyright year = 2011-2014 license = bsd %% module /* -o encoding Output encoding -i encoding Input encoding -p Input encoding is plain -w Normalize text. */ #include "dk3all.h" #include "dkt.h" $!trace-include /** Job structure for the "dkt cat" command. */ typedef struct { dk3_app_t *app; /**< Application structure. */ dkChar const * const *msg; /**< Localized messages. */ dkChar const * const *kwnl; /**< Keywords, not localized. */ dk3_option_set_t *opt; /**< Option set. */ dk3_stream_t *os; /**< Output stream. */ int enc_s; /**< Stdin encoding. */ int enc_f; /**< File encoding. */ int enc_o; /**< Output encoding. */ int o_nrm; /**< Option: Normalize. */ int o_car; /**< Option: Print carriage return+newline. */ int exval; /**< Exit status code. */ int f_nl; /**< Flag: Last char was newline. */ int f_eer; /**< Flag: Encoding error reported. */ int s1; /**< State of first processing level. */ int s2; /**< State of second processing level. */ } DKT_CAT_J; /** Configuration file options. */ static dkChar const * const dkt_cat_long_opt[] = { dkT("stdin-encoding"), dkT("file-encoding"), dkT("reset"), dkT("output-encoding"), dkT("carriage-return"), NULL }; /** Options for dkt cat. */ static dk3_option_t const dkt_cat_options[] = { { dkT('R'), dkT("reset"), 0 }, { dkT('i'), dkT("input-encoding"), 1 }, { dkT('p'), dkT("plain"), 0 }, { dkT('o'), dkT("output-encoding"), 1 }, { dkT('w'), dkT("normalize-text"), 0 }, { dkT('c'), dkT("carriage-return"), 0}, }; /** Number of options in the dkt_html_options array. */ static size_t const dkt_cat_szoptions = sizeof(dkt_cat_options)/sizeof(dk3_option_t); /** Initialize job structure. @param j Job structure to initialize. */ static void dkt_cat_job_init(DKT_CAT_J *j) { j->exval = DKT_RESULT_ERR_UNSPECIFIC; j->opt = NULL; j->enc_s = dk3app_get_input_stdin_encoding(j->app); j->enc_f = dk3app_get_input_file_encoding(j->app); j->enc_o = dk3app_get_output_encoding(j->app); j->o_nrm = 0; j->o_car = 0; j->os = NULL; j->f_nl = 0; j->s1 = 0; j->s2 = 0; j->f_eer = 0; } /** Reset job structure (-R or --reset option). @param j Job structure to reset. */ static void dkt_cat_job_reset(DKT_CAT_J *j) { j->enc_s = dk3app_get_default_stdin_encoding(j->app); j->enc_f = dk3app_get_default_file_encoding(j->app); j->enc_o = dk3app_get_output_encoding(j->app); j->o_nrm = 0; j->o_car = 0; } /** Clean up job structure after use. @param j Job structure. */ static void dkt_cat_job_cleanup(DKT_CAT_J *j) { if(j->opt) { dk3opt_close(j->opt); } j->opt = NULL; if(j->os) { dk3stream_close(j->os); } j->os = NULL; } /** Process one key/value pair. @param jv Pointer to job structure casted to void *. @param k Key. @param v Value. @return 1 to indicate success. */ static int dkt_cat_conf_line(void *jv, dkChar const *k, dkChar const *v) { int back = 1; DKT_CAT_J *j = NULL; $? "+ dkt_cat_conf_line k=\"%s\" v=\"%s\"", TR_STR(k), TR_STR(v) j = (DKT_CAT_J *)jv; switch(dk3str_array_index(dkt_cat_long_opt, k, 0)) { case 0: { if(v) { back = dkt_tool_set_encoding( j->app, &(j->enc_s), v, dk3app_get_input_stdin_encoding(j->app) ); } } break; case 1: { if(v) { back = dkt_tool_set_encoding( j->app, &(j->enc_f), v, dk3app_get_input_file_encoding(j->app) ); } } break; case 2: { dkt_cat_job_reset(j); } break; case 3: { if(v) { back = dkt_tool_set_encoding( j->app, &(j->enc_o), v, dk3app_get_output_encoding(j->app) ); } } break; case 4: { if(v) { if(dk3str_is_bool(v)) { j->o_car = (dk3str_is_on(v) ? 1 : 0); } } else { j->o_car = 1; } } break; default: { back = 0; } break; } $? "- dkt_cat_conf_line" return back; } /** Write one 32-bit character to output, convert if necessary. @param j Job structure. @param ch Character to write. */ static void dkt_cat_out(DKT_CAT_J *j, dk3_c32_t ch) { if(ch == (dk3_c32_t)0x0000000AUL) { if(j->o_car) { dk3stream_fputc_c32(j->os, (dk3_c32_t)0x0000000DUL, &(j->f_eer)); dk3stream_fputc_c32(j->os, ch, &(j->f_eer)); } else { dk3stream_fputc_c32(j->os, ch, &(j->f_eer)); } } else { dk3stream_fputc_c32(j->os, ch, &(j->f_eer)); } } /** Process one 32-bit character, normalize text if requested. @param j Job structure. @param ch Character to handle. */ static void dkt_cat_stage2(DKT_CAT_J *j, dk3_c32_t ch) { if(j->o_nrm) { switch(j->s2) { case 1: { if(ch == (dk3_c32_t)0x0000000AUL) { j->s2 = 4; } else { if(ch != (dk3_c32_t)0x00000009UL) { if(ch != (dk3_c32_t)0x00000020UL) { dkt_cat_out(j, ch); j->s2 = 2; } } } } break; case 2: { if(ch == (dk3_c32_t)0x00000009UL) { j->s2 = 3; } else { if(ch == (dk3_c32_t)0x00000020UL) { j->s2 = 3; } else { if(ch == (dk3_c32_t)0x0000000AUL) { dkt_cat_out(j, ch); j->s2 = 0; } else { dkt_cat_out(j, ch); } } } } break; case 3: { if(ch == (dk3_c32_t)0x0000000AUL) { dkt_cat_out(j, ch); j->s2 = 0; } else { if(ch != (dk3_c32_t)0x00000009UL) { if(ch != (dk3_c32_t)0x00000020UL) { dkt_cat_out(j, (dk3_c32_t)0x00000020UL); dkt_cat_out(j, ch); j->s2 = 2; } } } } break; case 4: { if(ch != (dk3_c32_t)0x00000009UL) { if(ch != (dk3_c32_t)0x00000020UL) { if(ch != (dk3_c32_t)0x0000000AUL) { dkt_cat_out(j, ch); j->s2 = 2; } } } } break; default: { if(ch == (dk3_c32_t)0x0000000AUL) { dkt_cat_out(j, ch); j->s2 = 1; } else { if(ch != (dk3_c32_t)0x00000009UL) { if(ch != (dk3_c32_t)0x00000020UL) { dkt_cat_out(j, ch); j->s2 = 2; } } } } break; } } else { dkt_cat_out(j, ch); } } /** Character handler function. In the handler function we replace carriage return/newline sequences by one newline. @param vj Job structure. @param ch Character to handle. @return 1 for success, 0 for error (can continue), -1 for error (abort). */ static int dkt_cat_char_handler(void *vj, dk3_c32_t ch) { int back = 1; DKT_CAT_J *j = NULL; j = (DKT_CAT_J *)vj; switch(j->s1) { case 1: { if(ch == (dk3_c32_t)0x0000000DUL) { dkt_cat_stage2(j, ch); } else { if(ch == (dk3_c32_t)0x0000000AUL) { dkt_cat_stage2(j, ch); j->s1 = 0; } else { dkt_cat_stage2(j, (dk3_c32_t)0x0000000DUL); dkt_cat_stage2(j, ch); j->s1 = 0; } } } break; default: { if(ch == (dk3_c32_t)0x0000000DUL) { j->s1 = 1; } else { dkt_cat_stage2(j, ch); } } break; } return back; } /** Process the command line arguments (options and file names). @param j Job structure. */ static int dkt_cat_process_arguments(DKT_CAT_J *j) { int back = 0; int xargc = 0; /* Number of command line arguments. */ int res = 0; /* Operation result. */ dkChar const * const *xargv = NULL; /* Command line arguments array. */ dkChar const *xenc = NULL; /* Encoding. */ xargc = dk3app_get_argc(j->app); xargv = dk3app_get_argv(j->app); xargv++; xargv++; xargc--; xargc--; j->opt = dk3opt_open_app( dkt_cat_options, dkt_cat_szoptions, dkT('\0'), NULL, xargc, xargv, j->app ); if(j->opt) { if(0 == dk3opt_get_error_code(j->opt)) { back = 1; if(dk3opt_is_set(j->opt, dkT('R'))) { dkt_cat_job_reset(j); } if(dk3opt_is_set(j->opt, dkT('i'))) { xenc = dk3opt_get_short_arg(j->opt, dkT('i')); if(xenc) { res = dkt_tool_set_encoding( j->app, &(j->enc_s), xenc, dk3app_get_input_stdin_encoding(j->app) ); if(res < 1) { back = 0; } else { j->enc_f = j->enc_s; } } else { back = 0; /* Missing argument. */ } if(dk3opt_is_set(j->opt, dkT('p'))) { back = 0; /* ERROR: -i and -d exclusive! */ dk3app_log_1(j->app, DK3_LL_ERROR, j->msg, 62); } } else { if(dk3opt_is_set(j->opt, dkT('p'))) { j->enc_s = j->enc_f = DK3_FILE_ENCODING_ASCII; } } if(dk3opt_is_set(j->opt, dkT('o'))) { xenc = dk3opt_get_short_arg(j->opt, dkT('o')); if(xenc) { res = dkt_tool_set_encoding( j->app, &(j->enc_o), xenc, dk3app_get_output_encoding(j->app) ); if(res < 1) { back = 0; } } else { back = 0; /* ERROR: Encoding missing */ } } if(dk3opt_is_set(j->opt, dkT('w'))) { j->o_nrm = 1; } if(dk3opt_is_set(j->opt, dkT('c'))) { j->o_car = 1; } } } return back; } /** Process one file. @param j Job structure. @param fn File name, no correction or expansion necessary. */ static void dkt_cat_process_one_file(DKT_CAT_J *j, dkChar const *fn) { $? "+ dkt_cat_process_one_file %s", TR_STR(fn) j->f_nl = 0; j->s1 = 0; j->s2 = 0; j->f_eer = 0; (void)dk3stream_process_filename_chars_app( (void *)j, dkt_cat_char_handler, fn, j->enc_f, j->app ); if(!(j->f_nl)) { (void)dkt_cat_char_handler((void *)j, (dk3_c32_t)0x0000000AUL); } $? "- dkt_cat_process_one_file" } /** Process all the file names specified on the command line. @param j Job structure. @param nfn Number of file names. */ static void dkt_cat_process_files(DKT_CAT_J *j, int nfn) { int i = 0; /* Current command line arg index. */ dkChar const *fn = NULL; /* File name. */ dkChar bu[DK3_MAX_PATH]; /* Buffer for file name. */ dk3_dir_t *fne = NULL; /* File name expander. */ dkChar const *en = NULL; /* Entry name (full file name). */ $? "+ dkt_cat_process_files" for(i = 0; i < nfn; i++) { fn = dk3opt_get_arg(j->opt, i); if(fn) { $? ". i = %d fn = \"%s\"", i, fn if(dk3str_len(fn) < DK3_SIZEOF(bu,dkChar)) { dk3str_cpy_not_overlapped(bu, fn); dk3str_correct_filename(bu); if(dk3sf_must_expand(bu)) { fne = dk3dir_fne_open_app(bu, j->app); if(fne) { if(dk3dir_get_number_of_files(fne) > 0) { while(dk3dir_get_next_file(fne)) { en = dk3dir_get_fullname(fne); if(en) { dkt_cat_process_one_file(j, en); } } } else { /* ERROR: No matching file name. */ dk3app_log_i3(j->app, DK3_LL_ERROR, 215, 216, fn); } dk3dir_close(fne); } } else { dkt_cat_process_one_file(j, bu); } } else { /* ERROR: File name too long */ dk3app_log_i3(j->app, DK3_LL_ERROR, 65, 66, fn); } } else { /* BUG */ } } $? "- dkt_cat_process_files" } /** Process standard input. @param j Job structure. */ static void dkt_cat_process_stdin(DKT_CAT_J *j) { j->f_nl = 0; j->s1 = 0; j->s2 = 0; j->f_eer = 0; dk3app_process_stdin_chars( j->app, (void *)j, dkt_cat_char_handler, j->enc_s ); if(!(j->f_nl)) { (void)dkt_cat_char_handler((void *)j, (dk3_c32_t)0x0000000AUL); } } /** Do the processing. @param j Job structure. */ static void dkt_cat_run(DKT_CAT_J *j) { int nfn = 0; /* Number of file names. */ #if DK3_ON_WINDOWS int oldmode = _O_TEXT; /* Old stdin mode. */ #endif #if DK3_ON_WINDOWS oldmode = _setmode(_fileno(stdout), _O_BINARY); #endif j->os = dk3stream_open_file_app(stdout, DK3_STREAM_FLAG_WRITE, j->app); if(j->os) { dk3stream_set_output_encoding(j->os, j->enc_o); switch(j->enc_o) { case DK3_FILE_ENCODING_UTF16_MSB_FIRST: case DK3_FILE_ENCODING_UTF16_LSB_FIRST: case DK3_FILE_ENCODING_UNICODE_MSB_FIRST: case DK3_FILE_ENCODING_UNICODE_LSB_FIRST: { #if DK3_ON_WINDOWS if(!_isatty(_fileno(stdout))) #else #if DK3_HAVE_ISATTY if(!isatty(1)) #endif #endif { dk3stream_write_byte_order_marker(j->os); } } break; } nfn = dk3opt_get_num_args(j->opt); if(nfn > 0) { dkt_cat_process_files(j, nfn); } else { dkt_cat_process_stdin(j); } dk3stream_close(j->os); j->os = NULL; } #if DK3_ON_WINDOWS _setmode(_fileno(stdout), oldmode); #endif } int dkt_cat( dk3_app_t *app, dkChar const *sn, dkChar const * const *msg, dkChar const * const *kwnl ) { int back = DKT_RESULT_ERR_UNSPECIFIC; DKT_CAT_J j; $? "+ dkt_cat" j.app = app; j.msg = msg; j.kwnl = kwnl; dkt_cat_job_init(&j); dkt_tool_read_conf(app, sn, (void *)(&j), dkt_cat_conf_line); if(dkt_cat_process_arguments(&j)) { dkt_cat_run(&j); } dkt_cat_job_cleanup(&j); $? "- dkt_cat %d", back return back; } /* vim: set ai sw=2 : */