]> Kevux Git Server - fll/commitdiff
Feature: bit_dump level 3 program
authorKevin Day <thekevinday@gmail.com>
Sun, 8 Sep 2019 04:25:50 +0000 (23:25 -0500)
committerKevin Day <thekevinday@gmail.com>
Sun, 8 Sep 2019 04:38:46 +0000 (23:38 -0500)
Provide a program to help analyze files, supporting UTF-8.

This should work similar to "hexdump" but is not intended to match it feature for feature.

Provides three byte printing modes (with plans for a fourth):
1) hexidecimal (default)
2) octal
3) binary
4) digit (planned)

Provides first and last byte selection support.

A width option is available for specifying the number of bytes to be printed on screen such that each byte is essentially a data column.
With a width of 16, then there would be 16 data columns, each displaying one byte.

Although similar to "hexdump", the first column in bit_dump represents the specific row number.

A text option is provided to display the bytes as a character (similar to how "hexdump" uses "-C").

A placeholder option is available for showing a placeholder where placeholder spaces would otherwise be printed.
A placeholder is printed to ensure alignment.
For example, a printable UTF-8 character that is 3-bytes wide would only visibly take up 1 character of space.
To keep the alignment with text to bytes accurate and consistent, two additional placeholder spaces are appended following the UTF-8 characte.

If the bytes terminate before an entire column set of bytes are printed, then spaces or placeholders are printed until the full column may be printed when in "text" mode.

This will detect and report invalid UTF-8 codes.

Handling printing the characters (via the text option) can be tricky.
There is more work needed to catch all cases.
Some cases cannot be handled if the character is wider than the expected width (causing alignment printing issues).

I am still a bit inexperienced with the intricacies of UTF-8 and I expect there to be issues in this first pass.

level_3/bit_dump/c/bit_dump.c [new file with mode: 0644]
level_3/bit_dump/c/bit_dump.h [new file with mode: 0644]
level_3/bit_dump/c/main.c [new file with mode: 0644]
level_3/bit_dump/c/private-bit_dump.c [new file with mode: 0644]
level_3/bit_dump/c/private-bit_dump.h [new file with mode: 0644]
level_3/bit_dump/data/build/dependencies [new file with mode: 0644]
level_3/bit_dump/data/build/settings [new file with mode: 0644]

diff --git a/level_3/bit_dump/c/bit_dump.c b/level_3/bit_dump/c/bit_dump.c
new file mode 100644 (file)
index 0000000..79c8c41
--- /dev/null
@@ -0,0 +1,280 @@
+#include <level_3/bit_dump.h>
+#include "private-bit_dump.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _di_bit_dump_print_help_
+  f_return_status bit_dump_print_help(const bit_dump_data data) {
+    fll_program_print_help_header(data.context, bit_dump_name_long, bit_dump_version);
+
+    fll_program_print_help_option(data.context, f_console_standard_short_help, f_console_standard_long_help, "         Print this help message.");
+    fll_program_print_help_option(data.context, f_console_standard_short_light, f_console_standard_long_light, "        Output using colors that show up better on light backgrounds");
+    fll_program_print_help_option(data.context, f_console_standard_short_dark, f_console_standard_long_dark, "         Output using colors that show up better on dark backgrounds");
+    fll_program_print_help_option(data.context, f_console_standard_short_no_color, f_console_standard_long_no_color, "     Do not output in color.");
+    fll_program_print_help_option(data.context, f_console_standard_short_version, f_console_standard_long_version, "      Print only the version number.");
+
+    printf("%c", f_string_eol);
+
+    fll_program_print_help_option(data.context, bit_dump_short_binary, bit_dump_long_binary, "       Display binary representation.");
+    fll_program_print_help_option(data.context, bit_dump_short_hex, bit_dump_long_hex, "          Display hexadecimal representation.");
+
+    fll_program_print_help_usage(data.context, bit_dump_name, "filename(s)");
+
+    printf("When using the ");
+    fl_color_print(f_standard_output, data.context.notable, data.context.reset, "--%s", bit_dump_long_text);
+    printf(" option, some UTF-8 characters may be replaced by your instance and cause display alignment issues.");
+
+    printf("%c%c", f_string_eol, f_string_eol);
+
+    printf("Special UTF-8 characters and non-spacing UTF-8 characters may be replaced with a space (or a placeholder when the ");
+    fl_color_print(f_standard_output, data.context.notable, data.context.reset, "--%s", bit_dump_long_placeholder);
+    printf(" option is used).");
+
+    printf("%c%c", f_string_eol, f_string_eol);
+
+    return f_none;
+  }
+#endif // _di_bit_dump_print_help_
+
+#ifndef _di_bit_dump_main_
+  f_return_status bit_dump_main(const f_array_length argc, const f_string argv[], bit_dump_data *data) {
+    f_status status = fll_program_process_parameters(argc, argv, data->parameters, bit_dump_total_parameters, bit_dump_parameter_no_color, bit_dump_parameter_light, bit_dump_parameter_dark, &data->remaining, &data->context);
+
+    if (f_status_is_error(status)) {
+      bit_dump_delete_data(data);
+      return f_status_set_error(status);
+    }
+
+    if (data->parameters[bit_dump_parameter_binary].result == f_console_result_found) {
+      if (data->parameters[bit_dump_parameter_hexidecimal].result == f_console_result_none) {
+        if (data->parameters[bit_dump_parameter_octal].result == f_console_result_none || data->parameters[bit_dump_parameter_binary].location > data->parameters[bit_dump_parameter_octal].location) {
+          data->mode = bit_dump_mode_binary;
+        }
+        else if (data->parameters[bit_dump_parameter_binary].location == data->parameters[bit_dump_parameter_octal].location && data->parameters[bit_dump_parameter_binary].location_sub > data->parameters[bit_dump_parameter_octal].location_sub) {
+          data->mode = bit_dump_mode_binary;
+        }
+        else {
+          data->mode = bit_dump_mode_octal;
+        }
+      }
+      else {
+        if (data->parameters[bit_dump_parameter_octal].result == f_console_result_none) {
+          if (data->parameters[bit_dump_parameter_binary].location > data->parameters[bit_dump_parameter_hexidecimal].location) {
+            data->mode = bit_dump_mode_binary;
+          }
+          else if (data->parameters[bit_dump_parameter_binary].location == data->parameters[bit_dump_parameter_hexidecimal].location && data->parameters[bit_dump_parameter_binary].location_sub > data->parameters[bit_dump_parameter_hexidecimal].location_sub) {
+            data->mode = bit_dump_mode_binary;
+          }
+          else {
+            data->mode = bit_dump_mode_hexidecimal;
+          }
+        }
+        else if (data->parameters[bit_dump_parameter_binary].location > data->parameters[bit_dump_parameter_octal].location) {
+          if (data->parameters[bit_dump_parameter_binary].location > data->parameters[bit_dump_parameter_hexidecimal].location) {
+            data->mode = bit_dump_mode_binary;
+          }
+          else if (data->parameters[bit_dump_parameter_binary].location == data->parameters[bit_dump_parameter_hexidecimal].location && data->parameters[bit_dump_parameter_binary].location_sub > data->parameters[bit_dump_parameter_hexidecimal].location_sub) {
+            data->mode = bit_dump_mode_binary;
+          }
+          else {
+            data->mode = bit_dump_mode_hexidecimal;
+          }
+        }
+        else if (data->parameters[bit_dump_parameter_binary].location == data->parameters[bit_dump_parameter_octal].location && data->parameters[bit_dump_parameter_binary].location_sub > data->parameters[bit_dump_parameter_octal].location_sub) {
+          if (data->parameters[bit_dump_parameter_binary].location > data->parameters[bit_dump_parameter_hexidecimal].location) {
+            data->mode = bit_dump_mode_binary;
+          }
+          else if (data->parameters[bit_dump_parameter_binary].location == data->parameters[bit_dump_parameter_hexidecimal].location && data->parameters[bit_dump_parameter_binary].location_sub > data->parameters[bit_dump_parameter_hexidecimal].location_sub) {
+            data->mode = bit_dump_mode_binary;
+          }
+          else {
+            data->mode = bit_dump_mode_hexidecimal;
+          }
+        }
+        else if (data->parameters[bit_dump_parameter_hexidecimal].location > data->parameters[bit_dump_parameter_octal].location) {
+          data->mode = bit_dump_mode_hexidecimal;
+        }
+        else if (data->parameters[bit_dump_parameter_hexidecimal].location == data->parameters[bit_dump_parameter_octal].location && data->parameters[bit_dump_parameter_hexidecimal].location_sub > data->parameters[bit_dump_parameter_octal].location_sub) {
+          data->mode = bit_dump_mode_hexidecimal;
+        }
+        else {
+          data->mode = bit_dump_mode_octal;
+        }
+      }
+    }
+    else if (data->parameters[bit_dump_parameter_hexidecimal].result == f_console_result_found) {
+      if (data->parameters[bit_dump_parameter_octal].result == f_console_result_none || data->parameters[bit_dump_parameter_hexidecimal].location > data->parameters[bit_dump_parameter_octal].location) {
+        data->mode = bit_dump_mode_hexidecimal;
+      }
+      else if (data->parameters[bit_dump_parameter_hexidecimal].location == data->parameters[bit_dump_parameter_octal].location && data->parameters[bit_dump_parameter_hexidecimal].location_sub > data->parameters[bit_dump_parameter_octal].location_sub) {
+        data->mode = bit_dump_mode_hexidecimal;
+      }
+      else {
+        data->mode = bit_dump_mode_octal;
+      }
+    }
+    else if (data->parameters[bit_dump_parameter_octal].result == f_console_result_found) {
+      data->mode = bit_dump_mode_octal;
+    }
+
+    status = f_none;
+
+    // execute parameter results.
+    if (data->parameters[bit_dump_parameter_help].result == f_console_result_found) {
+      bit_dump_print_help(*data);
+    }
+    else if (data->parameters[bit_dump_parameter_version].result == f_console_result_found) {
+      fll_program_print_version(bit_dump_version);
+    }
+    else if (data->remaining.used > 0 || data->process_pipe) {
+      if (data->parameters[bit_dump_parameter_width].result == f_console_result_found) {
+        fl_color_print_line(f_standard_output, data->context.error, data->context.reset, "Width option was specified but no width was given.");
+
+        bit_dump_delete_data(data);
+        return f_status_set_error(status);
+      }
+      else if (data->parameters[bit_dump_parameter_width].result == f_console_result_additional) {
+        uint64_t number = atoll(argv[data->parameters[bit_dump_parameter_width].additional.array[0]]);
+        if (number < 1 || number >= 0xfb) {
+          fl_color_print_line(f_standard_output, data->context.error, data->context.reset, "Width option can only be a number between 0 and 251.");
+
+          bit_dump_delete_data(data);
+          return f_status_set_error(status);
+        }
+
+        data->width = (uint8_t) number;
+      }
+
+      if (data->parameters[bit_dump_parameter_first].result == f_console_result_found) {
+        fl_color_print_line(f_standard_output, data->context.error, data->context.reset, "First option was specified but no number was given.");
+
+        bit_dump_delete_data(data);
+        return f_status_set_error(status);
+      }
+      else if (data->parameters[bit_dump_parameter_first].result == f_console_result_additional) {
+        uint64_t number = atoll(argv[data->parameters[bit_dump_parameter_first].additional.array[0]]);
+        if (number < 1 || number >= 0xffffffffffffffff) {
+          fl_color_print_line(f_standard_output, data->context.error, data->context.reset, "First option can only be a number between 0 and 18446744073709551615.");
+
+          bit_dump_delete_data(data);
+          return f_status_set_error(status);
+        }
+
+        data->first = number;
+      }
+
+      if (data->parameters[bit_dump_parameter_last].result == f_console_result_found) {
+        fl_color_print_line(f_standard_output, data->context.error, data->context.reset, "Last option was specified but no number was given.");
+
+        bit_dump_delete_data(data);
+        return f_status_set_error(status);
+      }
+      else if (data->parameters[bit_dump_parameter_last].result == f_console_result_additional) {
+        uint64_t number = atoll(argv[data->parameters[bit_dump_parameter_last].additional.array[0]]);
+        if (number < 1 || number >= 0xffffffffffffffff) {
+          fl_color_print_line(f_standard_output, data->context.error, data->context.reset, "Last option can only be a number between 0 and 18446744073709551615.");
+
+          bit_dump_delete_data(data);
+          return f_status_set_error(status);
+        }
+
+        data->last = number;
+      }
+
+      if (data->first > data->last) {
+          fl_color_print(f_standard_output, data->context.error, data->context.reset, "First option (");
+          fl_color_print(f_standard_output, data->context.notable, data->context.reset, "%d", data->first);
+          fl_color_print(f_standard_output, data->context.error, data->context.reset, ") cannot be greater than Last option (");
+          fl_color_print(f_standard_output, data->context.notable, data->context.reset, "%d", data->last);
+          fl_color_print_line(f_standard_output, data->context.error, data->context.reset, ").");
+
+          bit_dump_delete_data(data);
+          return f_status_set_error(status);
+      }
+
+      if (data->process_pipe) {
+        // TODO: how should this be done?
+      }
+
+      if (data->remaining.used > 0) {
+        // pre-process remaining arguments to ensure that they all files exist before processing.
+        {
+          f_status missing_files = f_none;
+
+          for (f_array_length counter = 0; counter < data->remaining.used; counter++) {
+            status = f_file_exists(argv[data->remaining.array[counter]]);
+            if (status == f_false || f_status_is_error(status)) {
+              if (missing_files == f_none) {
+                missing_files = status;
+              }
+
+              bit_dump_print_file_error(data->context, status, "f_file_exists", argv[data->remaining.array[counter]]);
+            }
+          } // for
+
+          if (missing_files != f_none) {
+            status = f_status_set_error(missing_files);
+
+            bit_dump_delete_data(data);
+            return status;
+          }
+        }
+
+        for (f_array_length counter = 0; counter < data->remaining.used; counter++) {
+          f_file file = f_file_initialize;
+
+          status = f_file_open(&file, argv[data->remaining.array[counter]]);
+          if (f_status_is_error(status)) {
+            bit_dump_print_file_error(data->context, status, "f_file_open", argv[data->remaining.array[counter]]);
+            bit_dump_delete_data(data);
+            return status;
+          }
+
+          printf("%c", f_string_eol);
+          fl_color_print(f_standard_output, data->context.title, data->context.reset, "Byte Dump of: ");
+          fl_color_print_line(f_standard_output, data->context.notable, data->context.reset, "%s", argv[data->remaining.array[counter]]);
+
+          status = bit_dump_file(*data, argv[data->remaining.array[counter]], file);
+
+          f_file_close(&file);
+
+          if (f_status_is_error(status)) {
+            bit_dump_delete_data(data);
+            return status;
+          }
+        } // for
+      }
+      else {
+        status = f_false;
+      }
+    }
+    else {
+      fl_color_print_line(f_standard_error, data->context.error, data->context.reset, "ERROR: you failed to specify an error code.");
+      status = f_status_set_error(f_invalid_parameter);
+    }
+
+    bit_dump_delete_data(data);
+    return status;
+  }
+#endif // _di_bit_dump_main_
+
+#ifndef _di_bit_dump_delete_data_
+  f_return_status bit_dump_delete_data(bit_dump_data *data) {
+    f_status status = f_none;
+
+    for (f_string_length i = 0; i < bit_dump_total_parameters; i++) {
+      f_macro_string_lengths_delete(status, data->parameters[i].additional);
+    } // for
+
+    f_macro_string_lengths_delete(status, data->remaining);
+    fl_macro_color_context_delete(status, data->context);
+
+    return f_none;
+  }
+#endif // _di_bit_dump_delete_data_
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
diff --git a/level_3/bit_dump/c/bit_dump.h b/level_3/bit_dump/c/bit_dump.h
new file mode 100644 (file)
index 0000000..cddcf74
--- /dev/null
@@ -0,0 +1,259 @@
+/**
+ * FLL - Level 3
+ *
+ * Project: Bit Dump
+ * API Version: 0.5
+ * Licenses: lgplv2.1
+ *
+ * This is intendend to support Unicode 12.1.
+ *
+ * When using "text" mode, this program attempts to translate UTF-8 sequences such that certain codes don't cause printing problems.
+ * There may be cases where there are unknown codes that get printed and the invalid UTF-8 marker may be displayed not by this program but instead by the shell or some other program.
+ * This can potentially be seen with the program "less".
+ * The solution is to add the invalid character codes to this project so that this project can properly print the invalid UTF-8 marker and therefore properly display the information.
+ */
+#ifndef _bit_dump_h
+
+// libc includes
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+// fll-0 includes
+#include <level_0/console.h>
+#include <level_0/conversion.h>
+#include <level_0/pipe.h>
+#include <level_0/print.h>
+#include <level_0/status.h>
+#include <level_0/string.h>
+#include <level_0/type.h>
+#include <level_0/utf.h>
+
+// fll-1 includes
+#include <level_1/color.h>
+#include <level_1/console.h>
+#include <level_1/string.h>
+#include <level_1/utf.h>
+
+// fll-2 includes
+#include <level_2/program.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _di_bit_dump_version_
+  #define bit_dump_major_version "0"
+  #define bit_dump_minor_version "5"
+  #define bit_dump_micro_version "0"
+  #define bit_dump_version bit_dump_major_version "." bit_dump_minor_version "." bit_dump_micro_version
+#endif // _di_bit_dump_version_
+
+#ifndef _di_bit_dump_name_
+  #define bit_dump_name      "bit_dump"
+  #define bit_dump_name_long "Bit Dump"
+#endif // _di_bit_dump_name_
+
+#ifndef _di_bit_dump_default_allocation_step_
+  // provide a UTF-8 friendly allocation step.
+  #define bit_dump_default_allocation_step 4
+#endif // _di_bit_dump_default_allocation_step_
+
+#ifndef _di_bit_dump_defines_
+  enum {
+    bit_dump_mode_hexidecimal,
+    bit_dump_mode_octal,
+    bit_dump_mode_binary,
+    bit_dump_mode_digit,
+  };
+
+  #define bit_dump_sequence_acknowledge               "␆"
+  #define bit_dump_sequence_backspace                 "␈"
+  #define bit_dump_sequence_bell                      "␇"
+  #define bit_dump_sequence_cancel                    "␘"
+  #define bit_dump_sequence_carriage_return           "␍"
+  #define bit_dump_sequence_data_link_escape          "␐"
+  #define bit_dump_sequence_delete                    "␡"
+  #define bit_dump_sequence_device_control_1          "␑"
+  #define bit_dump_sequence_device_control_2          "␒"
+  #define bit_dump_sequence_device_control_3          "␓"
+  #define bit_dump_sequence_device_control_4          "␔"
+  #define bit_dump_sequence_end_of_enquiry            "␅"
+  #define bit_dump_sequence_end_of_medium             "␙"
+  #define bit_dump_sequence_end_of_text               "␃"
+  #define bit_dump_sequence_end_of_transmission       "␄"
+  #define bit_dump_sequence_end_of_transmission_block "␗"
+  #define bit_dump_sequence_escape                    "␛"
+  #define bit_dump_sequence_file_separator            "␜"
+  #define bit_dump_sequence_form_feed                 "␌"
+  #define bit_dump_sequence_group_separator           "␝"
+  #define bit_dump_sequence_line_feed                 "␊"
+  #define bit_dump_sequence_negative_acknowledge      "␕"
+  #define bit_dump_sequence_new_line                  "␤"
+  #define bit_dump_sequence_null                      "␀"
+  #define bit_dump_sequence_record_separator          "␞"
+  #define bit_dump_sequence_shift_in                  "␏"
+  #define bit_dump_sequence_shift_out                 "␎"
+  #define bit_dump_sequence_space                     "␠"
+  #define bit_dump_sequence_start_of_header           "␁"
+  #define bit_dump_sequence_start_of_text             "␂"
+  #define bit_dump_sequence_substitute                "␚"
+  #define bit_dump_sequence_synchronous_idle          "␖"
+  #define bit_dump_sequence_tab                       "␉"
+  #define bit_dump_sequence_tab_vertical              "␋"
+  #define bit_dump_sequence_unit_separator            "␟"
+  #define bit_dump_sequence_utf_bom                   "␂"
+
+  #define bit_dump_character_wall        "|"
+  #define bit_dump_character_placeholder "␣" // other likely choices: (substitute form 1: '␚', substitute form 2: '␦').
+  #define bit_dump_character_incomplete  "�"
+  #define bit_dump_character_unused      "�"
+
+  #define bit_dump_short_binary      "b"
+  #define bit_dump_short_hex         "x"
+  #define bit_dump_short_octal       "o"
+
+  #define bit_dump_short_first       "f"
+  #define bit_dump_short_last        "l"
+  #define bit_dump_short_text        "t"
+  #define bit_dump_short_width       "w"
+  #define bit_dump_short_placeholder "p"
+
+  #define bit_dump_long_binary       "binary"
+  #define bit_dump_long_hex          "hex"
+  #define bit_dump_long_octal        "octal"
+
+  #define bit_dump_long_first        "first"       // first offset byte size.
+  #define bit_dump_long_last         "last"        // last offset byte size.
+  #define bit_dump_long_width        "width"       // number of characters to display per row.
+
+  #define bit_dump_long_text         "text"        // display text
+  #define bit_dump_long_placeholder  "placeholder" // display (colored) placeholders to signify codes that are UTF-8 fragments.
+
+  enum {
+    bit_dump_parameter_help,
+    bit_dump_parameter_light,
+    bit_dump_parameter_dark,
+    bit_dump_parameter_no_color,
+    bit_dump_parameter_version,
+
+    bit_dump_parameter_binary,
+    bit_dump_parameter_hexidecimal,
+    bit_dump_parameter_octal,
+    // @todo: add digit print support bit_dump_parameter_digit,
+
+    bit_dump_parameter_first,
+    bit_dump_parameter_last,
+    bit_dump_parameter_width,
+
+    bit_dump_parameter_text,
+    bit_dump_parameter_placeholder,
+  };
+
+  #define bit_dump_console_parameter_initialize \
+    { \
+      f_console_parameter_initialize(f_console_standard_short_help, f_console_standard_long_help, 0, 0, f_console_type_normal), \
+      f_console_parameter_initialize(f_console_standard_short_light, f_console_standard_long_light, 0, 0, f_console_type_inverse), \
+      f_console_parameter_initialize(f_console_standard_short_dark, f_console_standard_long_dark, 0, 0, f_console_type_inverse), \
+      f_console_parameter_initialize(f_console_standard_short_no_color, f_console_standard_long_no_color, 0, 0, f_console_type_inverse), \
+      f_console_parameter_initialize(f_console_standard_short_version, f_console_standard_long_version, 0, 0, f_console_type_inverse), \
+      f_console_parameter_initialize(bit_dump_short_binary, bit_dump_long_binary, 0, 0, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_hex, bit_dump_long_hex, 0, 0, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_octal, bit_dump_long_octal, 0, 0, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_first, bit_dump_long_first, 0, 1, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_last, bit_dump_long_last, 0, 1, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_width, bit_dump_long_width, 0, 1, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_text, bit_dump_long_text, 0, 0, f_console_type_normal), \
+      f_console_parameter_initialize(bit_dump_short_placeholder, bit_dump_long_placeholder, 0, 0, f_console_type_normal), \
+    }
+
+  #define bit_dump_total_parameters 13
+#endif // _di_bit_dump_defines_
+
+#ifndef _di_bit_dump_data_
+  typedef struct {
+    f_console_parameter parameters[bit_dump_total_parameters];
+
+    f_string_lengths remaining;
+    f_bool process_pipe;
+
+    uint64_t first;
+    uint64_t last;
+    uint8_t  width;
+    uint8_t  mode;
+
+    fl_color_context context;
+  } bit_dump_data;
+
+  #define bit_dump_data_initialize \
+    { \
+      bit_dump_console_parameter_initialize, \
+      f_string_lengths_initialize, \
+      f_false, \
+      0, \
+      0, \
+      8, \
+      bit_dump_mode_hexidecimal, \
+      fl_color_context_initialize, \
+    }
+#endif // _di_bit_dump_data_
+
+/**
+ * Print help to standard output.
+ *
+ * @param data
+ *   The program data.
+ *
+ * @return
+ *   f_none on success.
+ */
+#ifndef _di_bit_dump_print_help_
+  extern f_return_status bit_dump_print_help(const bit_dump_data data);
+#endif // _di_bit_dump_print_help_
+
+/**
+ * Execute main program.
+ *
+ * Be sure to call bit_dump_delete_data() after executing this.
+ *
+ * @param argc
+ *   The number of parameters passed to the process.
+ * @param argv
+ *   The parameters passed to the process.
+ * @param data
+ *   The program data.
+ *
+ * @return
+ *   f_none on success.
+ *   Status codes (with error bit) are returned on any problem.
+ *
+ * @see bit_dump_delete_data()
+ */
+#ifndef _di_bit_dump_main_
+  extern f_return_status bit_dump_main(const f_array_length argc, const f_string argv[], bit_dump_data *data);
+#endif // _di_bit_dump_main_
+
+/**
+ * Deallocate data.
+ *
+ * Be sure to call this after executing bit_dump_main().
+ *
+ * @param data
+ *   The program data.
+ *
+ * @return
+ *   f_none on success.
+ *   Status codes (with error bit) are returned on any problem.
+ *
+ * @see bit_dump_main()
+ */
+#ifndef _di_bit_dump_delete_data_
+  extern f_return_status bit_dump_delete_data(bit_dump_data *data);
+#endif // _di_bit_dump_delete_data_
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // _bit_dump_h
diff --git a/level_3/bit_dump/c/main.c b/level_3/bit_dump/c/main.c
new file mode 100644 (file)
index 0000000..f6c10b2
--- /dev/null
@@ -0,0 +1,17 @@
+#include <level_3/bit_dump.h>
+
+int main(const f_array_length argc, const f_string argv[]) {
+  bit_dump_data data = bit_dump_data_initialize;
+
+  if (f_pipe_exists()) {
+    data.process_pipe = f_true;
+  }
+
+  f_status status = bit_dump_main(argc, argv, &data);
+
+  if (f_status_is_error(status)) {
+    return 1;
+  }
+
+  return 0;
+}
diff --git a/level_3/bit_dump/c/private-bit_dump.c b/level_3/bit_dump/c/private-bit_dump.c
new file mode 100644 (file)
index 0000000..5eae239
--- /dev/null
@@ -0,0 +1,756 @@
+/**
+ * Private source file for firewall.c.
+ */
+#include <level_3/bit_dump.h>
+#include "private-bit_dump.h"
+
+#ifndef _di_bit_dump_file_
+  f_return_status bit_dump_file(const bit_dump_data data, const f_string file_name, f_file file) {
+    f_status status = f_none;
+
+    uint64_t position = data.first;
+    uint64_t row = 0;
+    uint8_t size = 0;
+    uint8_t byte = 0;
+    uint8_t column = 0;
+
+    int8_t width_utf = -1;
+    int8_t width_current = 0;
+    int8_t width_count = 0;
+
+    // UTF-8 Characters bytes may overflow beyond the data.width.
+    // These overflowed bytes should still have placeholders printed in the next text-mode print.
+    uint8_t previous_bytes = 0;
+    uint8_t previous_invalid = 0;
+
+    f_bool character_reset = 0;
+    f_bool found_invalid_utf = f_false;
+
+    // Store the current character data until it can be printed.
+    f_utf_string_dynamic characters = f_utf_string_dynamic_initialize;
+    f_utf_character character_array[data.width];
+    f_utf_string_length character_current = 0;
+
+    memset(&character_array, 0, sizeof(f_utf_character) * data.width);
+    characters.string = character_array;
+    characters.used = 0;
+    characters.size = data.width;
+
+    // Record when a character is invalid.
+    uint8_t invalid[data.width];
+    memset(&invalid, 0, sizeof(uint8_t) * data.width);
+
+    fseek(file.address, data.first, SEEK_SET);
+
+    while ((size = read(file.id, &byte, 1)) > 0) {
+      // Storing the next character is designated by width_utf == -1.
+      if (width_utf == -1) {
+        width_utf = f_macro_utf_byte_width_is(byte);
+        width_count = 0;
+
+        // The character is reset, the characters.used is to be reset.
+        if (character_reset) {
+          characters.used = 0;
+          character_reset = f_false;
+          memset(&invalid, 0, sizeof(uint8_t) * data.width);
+        }
+
+        character_current = characters.used;
+        characters.used++;
+
+        invalid[character_current] = 0;
+      }
+
+      // When width_count == 0, then this is that start of a new character sequence.
+      if (width_count == 0) {
+        characters.string[character_current] = f_macro_utf_character_from_char_1(byte);
+        width_count = 1;
+
+        // The first character in a UTF-8 sequence cannot have a width of 1.
+        if (width_utf == 1) {
+          found_invalid_utf = f_true;
+          invalid[character_current] = 1;
+        }
+        // UTF-8 characters with width of 4 cannot have any characters of 0x8f as the first byte.
+        else if (width_utf == 4 && byte == 0x8f) {
+          found_invalid_utf = f_true;
+          invalid[character_current] = width_utf;
+        }
+        // These are not defined in Unicode, and so are considered invalid in UTF-8, regardless of their width_utf.
+        else if (byte >= 0xf5) {
+          found_invalid_utf = f_true;
+          invalid[character_current] = width_utf;
+        }
+        // Sequences that start with 0xc1 are invalid because UTF-8 does not support overlong ASCII.
+        else if (byte == 0xc1) {
+          found_invalid_utf = f_true;
+          invalid[character_current] = width_utf;
+        }
+        // Process the UTF-8 character.
+        else if (width_utf > 1) {
+          position++;
+
+          if (data.last > 0 && position > data.last) break;
+
+          continue;
+        }
+      }
+      // Process a UTF-8 character fragment.
+      else if (width_count < width_utf) {
+        width_current = f_macro_utf_byte_width_is(byte);
+
+        if (width_count == 1) {
+          characters.string[character_current] |= f_macro_utf_character_from_char_2(byte);
+        }
+        else if (width_count == 2) {
+          characters.string[character_current] |= f_macro_utf_character_from_char_3(byte);
+        }
+        else if (width_count == 3) {
+          characters.string[character_current] |= f_macro_utf_character_from_char_4(byte);
+        }
+
+        width_count++;
+
+        // UTF-8 character fragments must have a width of 1 (and ASCII characters can only be the first character in a sequence).
+        if (width_current == 1) {
+          // Grab the next UTF-8 character fragment if the entire sequence is not collected yet.
+          if (width_count < width_utf) {
+            position++;
+
+            if (data.last > 0 && position > data.last) break;
+
+            continue;
+          }
+        }
+        else {
+          found_invalid_utf = f_true;
+          invalid[character_current] = width_utf;
+        }
+      }
+
+      // At this point: an ASCII character is collected, the entire UTF-8 character sequence is collected, or an invalid UTF-8 was processed.
+
+      // Handle special case invalid situations, 0xc0 and 0xc1 are used for two-byte encoding of a 7-bit ASCII but are considered invalid by UTF-8.
+      // Does not include 0xc0 0x80 because this is considered a overlong NULL in UTF-8, which is a valid NULL.
+      if (width_utf == 2 && characters.string[character_current] > 0xc0800000 && characters.string[character_current] <= 0xc0ff0000) {
+        found_invalid_utf = f_true;
+        invalid[character_current] = width_utf;
+      }
+      // The unicode codes U+D800 to U+DFFF are for "UTF-16 surrogate halves" which are not supported in UTF-8.
+      else if (width_utf == 3 && characters.string[character_current] > 0xefbfb000 && characters.string[character_current] <= 0xc0ff0000) {
+        found_invalid_utf = f_true;
+        invalid[character_current] = width_utf;
+      }
+
+      if (bit_dump_print_character_fragment(data, characters, invalid, width_utf, 1, &previous_bytes, &previous_invalid, &column, &row)) {
+        character_reset = f_true;
+      }
+
+      if (width_utf > 1) {
+        if (bit_dump_print_character_fragment(data, characters, invalid, width_utf, 2, &previous_bytes, &previous_invalid, &column, &row) == f_true) {
+          character_reset = f_true;
+        }
+
+        if (width_utf > 2) {
+          if (bit_dump_print_character_fragment(data, characters, invalid, width_utf, 3, &previous_bytes, &previous_invalid, &column, &row)) {
+            character_reset = f_true;
+          }
+
+          if (width_utf > 3) {
+            if (bit_dump_print_character_fragment(data, characters, invalid, width_utf, 4, &previous_bytes, &previous_invalid, &column, &row)) {
+              character_reset = f_true;
+            }
+          }
+        }
+      }
+
+      width_utf = -1;
+      position++;
+
+      if (data.last > 0 && position > data.last) break;
+    } // while
+
+    // Print placeholders to fill out the remaining line and then optionally print the text block.
+    if (column > 0 && column < data.width) {
+      previous_bytes = 0;
+      previous_invalid = 0;
+
+      while (column < data.width) {
+        if (data.mode == bit_dump_mode_hexidecimal) {
+          printf("   ");
+        }
+        else if (data.mode == bit_dump_mode_octal) {
+          printf("    ");
+        }
+        else if (data.mode == bit_dump_mode_binary) {
+          printf("         ");
+        }
+        else if (data.mode == bit_dump_mode_digit) {
+          // @todo
+        }
+
+        column++;
+
+        if (column < data.width) {
+          if (data.mode == bit_dump_mode_hexidecimal && column % 8 == 0) {
+            printf(" ");
+          }
+          else if (data.mode == bit_dump_mode_octal && column % 6 == 0) {
+            printf(" ");
+          }
+          else if (data.mode == bit_dump_mode_binary && column % 4 == 0) {
+            printf(" ");
+          }
+          else if (data.mode == bit_dump_mode_digit && column % 4 == 0) {
+            printf(" ");
+          }
+        }
+      } // while
+
+      if (data.parameters[bit_dump_parameter_text].result == f_console_result_found) {
+        bit_dump_print_text(data, characters, invalid, &previous_bytes, &previous_invalid);
+      }
+      else {
+        printf("%c", f_string_eol);
+      }
+    }
+
+    printf("%c", f_string_eol);
+
+    // make sure to flush standard out to help prevent standard error from causing poblems.
+    fflush(f_standard_output);
+
+    if (found_invalid_utf) {
+      fl_color_print(f_standard_error, data.context.error, data.context.reset, "Invalid UTF-8 codes were detected for file '");
+      fl_color_print(f_standard_error, data.context.notable, data.context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, data.context.error, data.context.reset, "'.");
+      printf("%c", f_string_eol);
+    }
+
+    if (size < 0) {
+      // @todo: determine what the error from read() is and display it.
+      fl_color_print(f_standard_error, data.context.error, data.context.reset, "ERROR: read() failed for '");
+      fl_color_print(f_standard_error, data.context.notable, data.context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, data.context.error, data.context.reset, "'.");
+      printf("%c", f_string_eol);
+      status = f_status_set_error(f_failure);
+    }
+
+    fflush(f_standard_error);
+
+    return status;
+  }
+#endif // _di_bit_dump_file_
+
+#ifndef _di_bit_dump_print_character_fragment_
+  f_bool bit_dump_print_character_fragment(const bit_dump_data data, const f_utf_string_dynamic characters, const uint8_t invalid[], const int8_t width_utf, const int8_t byte_current, uint8_t *previous_bytes, uint8_t *previous_invalid, uint8_t *column, uint64_t *row) {
+    uint8_t byte = 0;
+
+    f_bool reset = f_false;
+
+    f_utf_string_length character_current = characters.used - 1;
+
+    if (byte_current == 1) {
+      byte = f_macro_utf_character_to_char_1(characters.string[character_current]);
+    }
+    else if (byte_current == 2) {
+      byte = f_macro_utf_character_to_char_2(characters.string[character_current]);
+    }
+    else if (byte_current == 3) {
+      byte = f_macro_utf_character_to_char_3(characters.string[character_current]);
+    }
+    else if (byte_current == 4) {
+      byte = f_macro_utf_character_to_char_4(characters.string[character_current]);
+    }
+
+    if (*column == 0) {
+      fl_color_print(f_standard_output, data.context.notable, data.context.reset, "%016X ", (uint64_t) *row);
+    }
+
+    if (data.mode == bit_dump_mode_hexidecimal) {
+      if (invalid[character_current]) {
+        fl_color_print(f_standard_output, data.context.error, data.context.reset, " %02x", (uint8_t) byte);
+      }
+      else {
+        printf(" %02x", (uint8_t) byte);
+      }
+    }
+    else if (data.mode == bit_dump_mode_octal) {
+      if (invalid[character_current]) {
+        fl_color_print(f_standard_output, data.context.error, data.context.reset, " %03o", (uint8_t) byte);
+      }
+      else {
+        printf(" %03o", (uint8_t) byte);
+      }
+    }
+    else if (data.mode == bit_dump_mode_binary) {
+      char binary_string[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+      binary_string[0] = ((byte >> 7) & 0x01) ? '1' : '0';
+      binary_string[1] = ((byte >> 6) & 0x01) ? '1' : '0';
+      binary_string[2] = ((byte >> 5) & 0x01) ? '1' : '0';
+      binary_string[3] = ((byte >> 4) & 0x01) ? '1' : '0';
+      binary_string[4] = ((byte >> 3) & 0x01) ? '1' : '0';
+      binary_string[5] = ((byte >> 2) & 0x01) ? '1' : '0';
+      binary_string[6] = ((byte >> 1) & 0x01) ? '1' : '0';
+      binary_string[7] = (byte & 0x01) ? '1' : '0';
+
+      if (invalid[character_current]) {
+        fl_color_print(f_standard_output, data.context.error, data.context.reset, " %s", binary_string);
+      }
+      else {
+        printf(" %s", binary_string);
+      }
+    }
+    else if (data.mode == bit_dump_mode_digit) {
+      if (invalid[character_current]) {
+        fl_color_print(f_standard_output, data.context.error, data.context.reset, " %02d", (uint8_t) byte);
+      }
+      else {
+        printf(" %02d", (uint8_t) byte);
+      }
+    }
+
+    (*column)++;
+
+    if (*column == data.width) {
+      uint8_t bytes = 0;
+
+      if (byte_current < width_utf) {
+        bytes = width_utf - byte_current;
+      }
+
+      reset = f_true;
+
+      if (data.parameters[bit_dump_parameter_text].result == f_console_result_found) {
+        bit_dump_print_text(data, characters, invalid, previous_bytes, previous_invalid);
+      }
+      else {
+        printf("%c", f_string_eol);
+      }
+
+      *column = 0;
+      (*row)++;
+
+      if (bytes == 0) {
+        *previous_bytes = 0;
+        *previous_invalid = 0;
+      }
+      else {
+        *previous_bytes = bytes;
+        *previous_invalid = invalid[character_current];
+      }
+    }
+    else if (data.mode == bit_dump_mode_hexidecimal && *column % 8 == 0) {
+      printf(" ");
+    }
+    else if (data.mode == bit_dump_mode_octal && *column % 6 == 0) {
+      printf(" ");
+    }
+    else if (data.mode == bit_dump_mode_binary && *column % 4 == 0) {
+      printf(" ");
+    }
+    else if (data.mode == bit_dump_mode_digit && *column % 4 == 0) {
+      printf(" ");
+    }
+
+    return reset;
+  }
+#endif // _di_bit_dump_print_character_fragment_
+
+#ifndef _di_bit_dump_print_text_
+  void bit_dump_print_text(const bit_dump_data data, const f_utf_string_dynamic characters, const uint8_t invalid[], uint8_t *previous_bytes, uint8_t *previous_invalid) {
+    uint8_t j = 0;
+    uint8_t output = 0;
+    uint8_t width_utf = 0;
+
+    fl_color_print(f_standard_output, data.context.notable, data.context.reset, "  %s ", bit_dump_character_wall);
+
+    // Print placeholders for the remaining fragments of UTF-8 characters printed on previous lines.
+    {
+      uint8_t bytes_overflow = 0;
+
+      if ((*previous_bytes - 1) > data.width) {
+        bytes_overflow = (*previous_bytes) - 1 - data.width;
+      }
+
+      if (*previous_bytes > 0) {
+        if (data.parameters[bit_dump_parameter_placeholder].result == f_console_result_found) {
+          for (; j < *previous_bytes && j < data.width; j++) {
+            if (*previous_invalid) {
+              fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_placeholder);
+            }
+            else {
+              fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_character_placeholder);
+            }
+          } // for
+        }
+        else {
+          for (; j < *previous_bytes && j < data.width; j++) {
+            printf(" ");
+          } // for
+        }
+      }
+
+      if (bytes_overflow > 0) {
+        *previous_bytes = bytes_overflow;
+      }
+      else {
+        *previous_bytes = 0;
+        *previous_invalid = 0;
+      }
+    }
+
+    for (uint8_t i = 0; i < characters.used && j < data.width; i++, j++) {
+      output = f_macro_utf_character_to_char_1(characters.string[i]);
+      width_utf = f_macro_utf_byte_width_is(output);
+
+      if (invalid[i]) {
+        fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_incomplete);
+      }
+      else if (output == 0) {
+        printf("%s", bit_dump_sequence_null);
+      }
+      else if (output == 1) {
+        printf("%s", bit_dump_sequence_start_of_header);
+      }
+      else if (output == 2) {
+        printf("%s", bit_dump_sequence_start_of_text);
+      }
+      else if (output == 3) {
+        printf("%s", bit_dump_sequence_end_of_text);
+      }
+      else if (output == 4) {
+        printf("%s", bit_dump_sequence_end_of_transmission);
+      }
+      else if (output == 5) {
+        printf("%s", bit_dump_sequence_end_of_enquiry);
+      }
+      else if (output == 6) {
+        printf("%s", bit_dump_sequence_acknowledge);
+      }
+      else if (output == 7) {
+        printf("%s", bit_dump_sequence_bell);
+      }
+      else if (output == 8) {
+        printf("%s", bit_dump_sequence_backspace);
+      }
+      else if (output == 9) {
+        printf("%s", bit_dump_sequence_tab);
+      }
+      else if (output == 10) {
+        printf("%s", bit_dump_sequence_new_line);
+      }
+      else if (output == 11) {
+        printf("%s", bit_dump_sequence_tab_vertical);
+      }
+      else if (output == 12) {
+        printf("%s", bit_dump_sequence_form_feed);
+      }
+      else if (output == 13) {
+        printf("%s", bit_dump_sequence_carriage_return);
+      }
+      else if (output == 14) {
+        printf("%s", bit_dump_sequence_shift_out);
+      }
+      else if (output == 15) {
+        printf("%s", bit_dump_sequence_shift_in);
+      }
+      else if (output == 16) {
+        printf("%s", bit_dump_sequence_data_link_escape);
+      }
+      else if (output == 17) {
+        printf("%s", bit_dump_sequence_device_control_1);
+      }
+      else if (output == 18) {
+        printf("%s", bit_dump_sequence_device_control_2);
+      }
+      else if (output == 19) {
+        printf("%s", bit_dump_sequence_device_control_3);
+      }
+      else if (output == 20) {
+        printf("%s", bit_dump_sequence_device_control_4);
+      }
+      else if (output == 21) {
+        printf("%s", bit_dump_sequence_negative_acknowledge);
+      }
+      else if (output == 22) {
+        printf("%s", bit_dump_sequence_synchronous_idle);
+      }
+      else if (output == 23) {
+        printf("%s", bit_dump_sequence_end_of_transmission_block);
+      }
+      else if (output == 24) {
+        printf("%s", bit_dump_sequence_cancel);
+      }
+      else if (output == 25) {
+        printf("%s", bit_dump_sequence_end_of_medium);
+      }
+      else if (output == 26) {
+        printf("%s", bit_dump_sequence_substitute);
+      }
+      else if (output == 27) {
+        printf("%s", bit_dump_sequence_escape);
+      }
+      else if (output == 28) {
+        printf("%s", bit_dump_sequence_file_separator);
+      }
+      else if (output == 29) {
+        printf("%s", bit_dump_sequence_group_separator);
+      }
+      else if (output == 30) {
+        printf("%s", bit_dump_sequence_record_separator);
+      }
+      else if (output == 31) {
+        printf("%s", bit_dump_sequence_unit_separator);
+      }
+      else if (output == 32) {
+        printf("%s", bit_dump_sequence_space);
+      }
+      else if (output == 127) {
+        printf("%s", bit_dump_sequence_delete);
+      }
+      else if (f_utf_is_whitespace_character(characters.string[i]) == f_true) {
+        printf("%s", bit_dump_sequence_space);
+      }
+      else if (width_utf == 2 && characters.string[i] == 0xc0800000) {
+        // This is an "Overlong Null" and is a valid NULL character.
+        printf("%s", bit_dump_sequence_null);
+      }
+      else if (width_utf == 2 && characters.string[i] >= 0xcc800000 && characters.string[i] <= 0xcdaf0000) {
+        // Combining characters should not be combined here, instead display a space.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xe1aab000 && characters.string[i] <= 0xe1abbf00) {
+        // Combining characters should not be combined here, instead display a space.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xe1b78000 && characters.string[i] <= 0xe1b7bf00) {
+        // Combining characters should not be combined here, instead display a space.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xe2839000 && characters.string[i] <= 0xe283bf00) {
+        // Combining characters should not be combined here, instead display a space.
+        printf(" ");
+      }
+      else if (width_utf == 2 && characters.string[i] >= 0xd8900000 && characters.string[i] <= 0xd89a0000) {
+        // Combining characters should not be combined here, instead display a space.
+        printf(" ");
+      }
+      else if (width_utf == 2 && characters.string[i] >= 0xc2800000 && characters.string[i] <= 0xc29f0000) {
+        // Use space to represent unprintable Latin-1 supplement control codes.
+        // 0xc2a00000 happens to be the non-breaking space character and is explicitly handled above.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xefbfb000 && characters.string[i] <= 0xefbfbc00) {
+        // Use space to represent Specials codes.
+        // 0xefbfbd00 is excluded because it is printable (and is the "Replacement Character" code).
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xe290a700 && characters.string[i] <= 0xe290bf00) {
+        // Use space to represent Control Pictues codes that are not currently defined but are reserved.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xeda08000 && characters.string[i] <= 0xedadbf00) {
+        // Use space to represent High Surrogates codes.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xedae8000 && characters.string[i] <= 0xedafbf00) {
+        // Use space to represent High Private Use Surrogates codes.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xedb08000 && characters.string[i] <= 0xedbfbf00) {
+        // Use space to represent Low Surrogates codes.
+        printf(" ");
+      }
+      else if (width_utf == 3 && characters.string[i] >= 0xee808000 && characters.string[i] <= 0xefa3bf00) {
+        // Use space to represent Private Use Area codes.
+        printf(" ");
+      }
+      else if (width_utf == 4 && characters.string[i] >= 0xf09c80a0 && characters.string[i] <= 0xf09c80bd) {
+        // Use space to represent Vaiation Selectors Supplement codes.
+        printf(" ");
+      }
+      else if (width_utf == 4 && characters.string[i] >= 0xf09e8080 && characters.string[i] <= 0xf09fbfbf) {
+        // Use space to represent Supplemental Private Use Area-A codes.
+        printf(" ");
+      }
+      else if (width_utf == 4 && characters.string[i] >= 0xf0a08080 && characters.string[i] <= 0xf0a1bfbf) {
+        // Use space to represent Supplemental Private Use Area-B codes.
+        printf(" ");
+      }
+      else if (characters.string[i] == f_utf_character_mask_bom) {
+        fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_sequence_utf_bom);
+      }
+      else if (width_utf == 1) {
+        // print invalid placeholder for invalid UTF-8 widths.
+        if (invalid[i]) {
+          fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_incomplete);
+        }
+        else {
+          fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_character_incomplete);
+        }
+      }
+      else if (width_utf > 0) {
+        printf("%c", (uint8_t) output);
+
+        if (width_utf > 1) {
+          output = f_macro_utf_character_to_char_2(characters.string[i]);
+          printf("%c", (uint8_t) output);
+
+          if (width_utf > 2) {
+            output = f_macro_utf_character_to_char_3(characters.string[i]);
+            printf("%c", (uint8_t) output);
+
+            if (width_utf > 3) {
+              output = f_macro_utf_character_to_char_4(characters.string[i]);
+              printf("%c", (uint8_t) output);
+            }
+          }
+        }
+      }
+      else {
+        printf("%c", output);
+      }
+
+      // When using UTF-8 characters, the character columns will not line up, so print placeholders to simulate the bytes that are not printed, if necessary for alignment.
+      if (width_utf > 1 && j + 1 < data.width) {
+        if (data.parameters[bit_dump_parameter_placeholder].result == f_console_result_found) {
+          if (invalid[i]) {
+            fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_placeholder);
+          }
+          else {
+            fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_character_placeholder);
+          }
+        }
+        else {
+          printf(" ");
+        }
+
+        j++;
+
+        if (width_utf > 2 && j + 1 < data.width) {
+          if (data.parameters[bit_dump_parameter_placeholder].result == f_console_result_found) {
+            if (invalid[i]) {
+              fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_placeholder);
+            }
+            else {
+              fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_character_placeholder);
+            }
+          }
+          else {
+            printf(" ");
+          }
+
+          j++;
+
+          if (width_utf > 3 && j + 1 < data.width) {
+            if (data.parameters[bit_dump_parameter_placeholder].result == f_console_result_found) {
+              if (invalid[i]) {
+                fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_placeholder);
+              }
+              else {
+                fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_character_placeholder);
+              }
+            }
+            else {
+              printf(" ");
+            }
+
+            j++;
+          }
+        }
+      }
+    } // for
+
+    // Print placeholder for the remaining parts of the line.
+    if (data.parameters[bit_dump_parameter_placeholder].result == f_console_result_found) {
+      for (; j < data.width; j++) {
+        if (invalid[j]) {
+          fl_color_print(f_standard_output, data.context.error, data.context.reset, "%s", bit_dump_character_placeholder);
+        }
+        else {
+          fl_color_print(f_standard_output, data.context.warning, data.context.reset, "%s", bit_dump_character_placeholder);
+        }
+      } // for
+    }
+    else {
+      for (; j < data.width; j++) {
+          printf(" ");
+      } // for
+    }
+
+    fl_color_print(f_standard_output, data.context.notable, data.context.reset, " |");
+    printf("%c", f_string_eol);
+  }
+#endif // _di_bit_dump_file_
+
+#ifndef _di_bit_dump_print_file_error_
+  void bit_dump_print_file_error(const fl_color_context context, const f_status status, const f_string function, const f_string file_name) {
+    f_status error = f_status_set_fine(status);
+
+    if (error == f_false) {
+      fl_color_print(f_standard_error, context.error, context.reset, "ERROR: failed to find file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_invalid_parameter) {
+      fl_color_print(f_standard_error, context.error, context.reset, "INTERNAL ERROR: Invalid parameter when calling ", function, file_name);
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", function);
+      fl_color_print(f_standard_error, context.error, context.reset, "() for the file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_invalid_name) {
+      fl_color_print(f_standard_error, context.error, context.reset, "ERROR: Invalid filename '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_out_of_memory) {
+      fl_color_print(f_standard_error, context.error, context.reset, "CRITICAL ERROR: unable to allocate memory, while trying to access file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_overflow) {
+      fl_color_print(f_standard_error, context.error, context.reset, "ERROR: Overflow while trying to access file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_invalid_directory) {
+      fl_color_print(f_standard_error, context.error, context.reset, "ERROR: Invalid directory while trying to access file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_access_denied) {
+      fl_color_print(f_standard_error, context.error, context.reset, "ERROR: Access denied while trying to access file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    if (error == f_loop) {
+      fl_color_print(f_standard_error, context.error, context.reset, "ERROR: Loop while trying to access file '");
+      fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+      fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+      return;
+    }
+
+    fl_color_print(f_standard_error, context.error, context.reset, "UNKNOWN ERROR: (");
+    fl_color_print(f_standard_error, context.notable, context.reset, "%d", error);
+    fl_color_print(f_standard_error, context.error, context.reset, ") occurred for file '");
+    fl_color_print(f_standard_error, context.notable, context.reset, "%s", file_name);
+    fl_color_print_line(f_standard_error, context.error, context.reset, "'.");
+  }
+#endif // _di_bit_dump_print_file_error_
diff --git a/level_3/bit_dump/c/private-bit_dump.h b/level_3/bit_dump/c/private-bit_dump.h
new file mode 100644 (file)
index 0000000..ceb8119
--- /dev/null
@@ -0,0 +1,117 @@
+/**
+ * FLL - Level 3
+ *
+ * Project: Bit Dump
+ * API Version: 0.5
+ * Licenses: lgplv2.1
+ */
+#ifndef _PRIVATE_bit_dump_h
+#define _PRIVATE_bit_dump_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+/**
+ * Dump the contents of the file to standard out.
+ *
+ * @param data
+ *   The program data.
+ * @param file_name
+ *   The name of the file.
+ * @param file
+ *   Data for the file to print.
+ *
+ * @return
+ *   f_none on success.
+ *   f_failure (with error bit) on failure, usually when read() fails.
+ */
+#ifndef _di_bit_dump_file_
+  extern f_return_status bit_dump_file(const bit_dump_data data, const f_string file_name, f_file file);
+#endif // _di_bit_dump_file_
+
+/**
+ * Print a single character hex code and if the width is reached properly terminate the line.
+ *
+ * @param data
+ *   The program data.
+ * @param characters
+ *   An array of UTF-8 and ASCII characters.
+ * @param invalid
+ *   An array designating if a given character at the array index is invalid or not.
+ *   The values represent the number of bytes in which the invalid character is expected to take up.
+ * @param width_utf
+ *   The number of bytes that the character completely represents.
+ *   A value of 0 represents ASCII.
+ * @param byte_current
+ *   The UTF-8 character byte block to print, going from left to right first byte is 1, second byte is 2, etc...
+ *   A value of 1 is used for ASCII.
+ * @param previous_bytes
+ *   The number of previous bytes that overflowed from the previous line.
+ *   This is used to print the placeholders for the "text" option.
+ * @param previous_invalid
+ *   The specific invalid value provided representing the overflowed bytes.
+ *   This is used to print the placeholders for the "text" option.
+ * @param column
+ *   The current column that the character is being printed on.
+ *   This value is incremented to represent that the character is printed.
+ *   When the max width is reached bit_dump_print_text() is called and this value is reset.
+ * @param row
+ *   The current row that the character is being printed on.
+ *   When the max width is reached bit_dump_print_text() is called and this value is incremented.
+ *
+ * @see bit_dump_print_text()
+ */
+#ifndef _di_bit_dump_print_character_fragment_
+  extern f_bool bit_dump_print_character_fragment(const bit_dump_data data, const f_utf_string_dynamic characters, const uint8_t invalid[], const int8_t width_utf, const int8_t byte_current, uint8_t *previous_bytes, uint8_t *previous_invalid, uint8_t *column, uint64_t *row);
+#endif // _di_bit_dump_print_character_fragment_
+
+
+/**
+ * Print the text representation alongside the hex display.
+ *
+ * This should be called only when text mode is enabled.
+ *
+ * @param data
+ *   The program data.
+ * @param characters
+ *   An array of UTF-8 and ASCII characters.
+ * @param invalid
+ *   An array designating if a given character at the array index is invalid or not.
+ *   The values represent the number of bytes in which the invalid character is expected to take up.
+ * @param previous_bytes
+ *   The number of previous bytes that overflowed from the previous line.
+ *   This is used to print the placeholders.
+ * @param previous_invalid
+ *   The specific invalid value provided representing the overflowed bytes.
+ *   This is used to print the placeholders.
+ */
+#ifndef _di_bit_dump_print_text_
+  extern void bit_dump_print_text(const bit_dump_data data, const f_utf_string_dynamic characters, const uint8_t invalid[], uint8_t *previous_bytes, uint8_t *previous_invalid);
+#endif // _di_bit_dump_print_text_
+
+/**
+ * Print error messages related to a file.
+ *
+ * @param context
+ *   The color context codes.
+ * @param status
+ *   The status code.
+ * @param function
+ *   The name of the function that failed.
+ * @param file_name
+ *   The name of the file related to the error.
+ *
+ * @param
+ *   f_true if error has been printed.
+ *   f_false if error has not been printed.
+ */
+#ifndef _di_bit_dump_print_file_error_
+  extern void bit_dump_print_file_error(const fl_color_context context, const f_status status, const f_string function, const f_string file_name);
+#endif // _di_bit_dump_print_file_error_
+
+#endif // _PRIVATE_bit_dump_h
diff --git a/level_3/bit_dump/data/build/dependencies b/level_3/bit_dump/data/build/dependencies
new file mode 100644 (file)
index 0000000..19c7bd5
--- /dev/null
@@ -0,0 +1,17 @@
+f_type
+f_status
+f_memory
+f_string
+f_color
+f_console
+f_conversion
+f_file
+f_pipe
+f_print
+f_utf
+fl_color
+fl_console
+fl_file
+fl_string
+fl_utf
+fll_program
diff --git a/level_3/bit_dump/data/build/settings b/level_3/bit_dump/data/build/settings
new file mode 100644 (file)
index 0000000..20f93fc
--- /dev/null
@@ -0,0 +1,32 @@
+# fss-0000
+
+project_name bit_dump
+project_level 3
+
+version_major 0
+version_minor 5
+version_micro 0
+
+build_compiler gcc
+build_linker ar
+build_libraries -lc
+build_libraries_fll -lfll_program -lfl_utf -lfl_string -lfl_file -lfl_console -lfl_color -lf_utf -lf_print -lf_pipe -lf_file -lf_conversion -lf_console -lf_memory
+#build_libraries_fll-level -lfll_2 -lfll_1 -lfll_0
+#build_libraries_fll-monolithic -lfll
+build_sources_library bit_dump.c private-bit_dump.c
+build_sources_program main.c
+build_sources_headers bit_dump.h
+build_sources_bash
+build_sources_settings
+build_shared yes
+build_static yes
+
+defines_all
+defines_static
+defines_shared
+
+flags_all -z now -g
+flags_shared
+flags_static
+flags_library -fPIC
+flags_program -fPIE -lbit_dump