This is an oversight on my part.
There should be an operation to write to a file.
There are two forms of this new "write" operation.
1) Truncate a file (deletes all data within a file).
2) Append to a file.
A file is created if it does not already exist in both cases.
The "write" operation supports some standard escape sequences as well as some non-standard ones.
Standard Escape Sequences:
- "\f": Form Feed.
- "\n": New Line.
- "\r": Carriage Return.
- "\t": Tab.
- "\v": Vertical Tab.
- "\\": Backslash Character (may require additional slashes in certain circumstances.)
- "\0": NULL Character.
Non-Standard Escape Sequences:
- "\U+": Unicode Sequence (followed by a valid Unicode sequence with a minimum 4 hexidecimal digits and a maximum of 6 hexidecimal digits).
- "\U-": Terminate a Unicode Sequence, allowing for "\U+000A\U-5" to be equivalent to "\n5".
const f_string_static_t fake_make_operation_to_s = macro_f_string_static_t_initialize(FAKE_make_operation_to_s, 0, FAKE_make_operation_to_s_length);
const f_string_static_t fake_make_operation_top_s = macro_f_string_static_t_initialize(FAKE_make_operation_top_s, 0, FAKE_make_operation_top_s_length);
const f_string_static_t fake_make_operation_touch_s = macro_f_string_static_t_initialize(FAKE_make_operation_touch_s, 0, FAKE_make_operation_touch_s_length);
+ const f_string_static_t fake_make_operation_write_s = macro_f_string_static_t_initialize(FAKE_make_operation_write_s, 0, FAKE_make_operation_write_s_length);
const f_string_static_t fake_make_operation_argument_environment_s = macro_f_string_static_t_initialize(FAKE_make_operation_argument_environment_s, 0, FAKE_make_operation_argument_environment_s_length);
const f_string_static_t fake_make_operation_argument_failure_s = macro_f_string_static_t_initialize(FAKE_make_operation_argument_failure_s, 0, FAKE_make_operation_argument_failure_s_length);
#define FAKE_make_operation_to_s "to"
#define FAKE_make_operation_top_s "top"
#define FAKE_make_operation_touch_s "touch"
+ #define FAKE_make_operation_write_s "write"
#define FAKE_make_operation_and_s_length 3
#define FAKE_make_operation_break_s_length 5
#define FAKE_make_operation_to_s_length 2
#define FAKE_make_operation_top_s_length 3
#define FAKE_make_operation_touch_s_length 5
+ #define FAKE_make_operation_write_s_length 5
extern const f_string_static_t fake_make_operation_and_s;
extern const f_string_static_t fake_make_operation_break_s;
extern const f_string_static_t fake_make_operation_to_s;
extern const f_string_static_t fake_make_operation_top_s;
extern const f_string_static_t fake_make_operation_touch_s;
+ extern const f_string_static_t fake_make_operation_write_s;
enum {
fake_make_operation_type_none_e = 0,
fake_make_operation_type_to_e,
fake_make_operation_type_top_e,
fake_make_operation_type_touch_e,
+ fake_make_operation_type_write_e,
};
- #define fake_make_operation_total_d 34
+ // Total does not include "fake_make_operation_type_none_e".
+ #define fake_make_operation_total_d 35
#define FAKE_make_operation_argument_environment_s "environment"
#define FAKE_make_operation_argument_failure_s "failure"
fake_make_operation_to_s,
fake_make_operation_top_s,
fake_make_operation_touch_s,
+ fake_make_operation_write_s,
};
const uint8_t operations_type[] = {
fake_make_operation_type_to_e,
fake_make_operation_type_top_e,
fake_make_operation_type_touch_e,
+ fake_make_operation_type_write_e,
};
fake_state_process_t state_process = fake_state_process_t_initialize;
*status = fake_make_operate_process_type_touch(data_make, arguments);
}
+ if (state_process->operation == fake_make_operation_type_write_e) {
+ *status = fake_make_operate_process_type_write(data_make, arguments);
+ }
+
return 0;
}
#endif // _di_fake_make_operate_process_
+#ifndef _di_fake_make_operate_process_buffer_escape_
+ f_status_t fake_make_operate_process_buffer_escape(fake_make_data_t * const data_make, const f_string_static_t source, f_string_dynamic_t * const destination) {
+
+ destination->used = 0;
+
+ if (!source.used) return F_data_not;
+
+ f_status_t status = f_string_dynamic_increase_by(source.used, destination);
+ if (F_status_is_error(status)) return status;
+
+ for (f_array_length_t i = 0; i < source.used; ++i) {
+
+ // NULL characters are from delimited characters and must be skipped.
+ if (!source.string[i]) continue;
+
+ if (source.string[i] == f_string_ascii_slash_backward_s.string[0]) {
+
+ // A slash by itself at the end of the string is invalid.
+ if (++i >= source.used) break;
+
+ status = f_string_dynamic_increase_by(F_memory_default_allocation_small_d, destination);
+ if (F_status_is_error(status)) return status;
+
+ if (source.string[i] == f_string_ascii_slash_backward_s.string[0]) {
+ destination->string[destination->used++] = f_string_ascii_slash_backward_s.string[0];
+ }
+ else if (source.string[i] == f_string_ascii_n_s.string[0]) {
+ destination->string[destination->used++] = f_string_ascii_feed_line_s.string[0];
+ }
+ else if (source.string[i] == f_string_ascii_r_s.string[0]) {
+ destination->string[destination->used++] = f_string_ascii_return_carriage_s.string[0];
+ }
+ else if (source.string[i] == f_string_ascii_t_s.string[0]) {
+ destination->string[destination->used++] = f_string_ascii_tab_horizontal_s.string[0];
+ }
+ else if (source.string[i] == f_string_ascii_v_s.string[0]) {
+ destination->string[destination->used++] = f_string_ascii_tab_vertical_s.string[0];
+ }
+ else if (source.string[i] == f_string_ascii_0_s.string[0]) {
+ destination->string[destination->used++] = f_string_null_s.string[0];
+ }
+ else if (source.string[i] == f_string_ascii_U_s.string[0]) {
+
+ // At the end of the string before a \U+XXXX sequence is completed is invalid.
+ if (++i >= source.used) break;
+
+ if (source.string[i] == f_string_ascii_plus_s.string[0]) {
+
+ // At the end of the string before a \U+XXXX sequence is completed is invalid.
+ if (i + 4 >= source.used) break;
+
+ ++i;
+
+ // The max Unicode sequence length is "U+XXXXXX".
+ char buffer_string[9] = { f_string_ascii_U_s.string[0], f_string_ascii_plus_s.string[0], 0, 0, 0, 0, 0, 0, 0 };
+ f_string_static_t buffer = macro_f_string_static_t_initialize(buffer_string, 0, 2);
+
+ for (uint8_t j = 2; i < source.used && j < 8; ) {
+
+ if (!isdigit(source.string[i])) {
+ if (!(source.string[i] == f_string_ascii_A_s.string[0] ||
+ source.string[i] == f_string_ascii_B_s.string[0] ||
+ source.string[i] == f_string_ascii_C_s.string[0] ||
+ source.string[i] == f_string_ascii_D_s.string[0] ||
+ source.string[i] == f_string_ascii_E_s.string[0] ||
+ source.string[i] == f_string_ascii_F_s.string[0])) {
+
+ if (!(source.string[i] == f_string_ascii_a_s.string[0] ||
+ source.string[i] == f_string_ascii_b_s.string[0] ||
+ source.string[i] == f_string_ascii_c_s.string[0] ||
+ source.string[i] == f_string_ascii_d_s.string[0] ||
+ source.string[i] == f_string_ascii_e_s.string[0] ||
+ source.string[i] == f_string_ascii_f_s.string[0])) {
+
+ --i;
+
+ break;
+ }
+ }
+ }
+
+ buffer_string[j++] = source.string[i++];
+ ++buffer.used;
+ } // for
+
+ if (buffer.used > 2) {
+ f_utf_char_t codepoint = 0;
+
+ status = f_utf_unicode_string_to(buffer.string, buffer.used, &codepoint);
+
+ if (F_status_is_error(status)) {
+ status = F_status_set_fine(status);
+
+ if (!(status == F_failure || status == F_utf_not || status == F_complete_not_utf || status == F_utf_fragment || status == F_valid_not)) {
+ status = F_status_set_error(status);
+
+ break;
+ }
+ }
+ else {
+
+ // Reserve 4-bytes (the max size of a Unicode UTF-8 sequence).
+ status = f_string_dynamic_increase_by(4, destination);
+ if (F_status_is_error(status)) return status;
+
+ if (!codepoint) {
+ destination->string[destination->used++] = f_string_null_s.string[0];
+ }
+ else {
+ {
+ f_string_t address = destination->string + destination->used;
+
+ status = f_utf_unicode_from(codepoint, 4, &address);
+ }
+
+ if (F_status_is_error(status)) {
+ destination->string[destination->used] = 0;
+ }
+ else {
+ if (codepoint < 0x80) {
+ destination->used += 1;
+ }
+ else if (codepoint < 0x800) {
+ destination->used += 2;
+ }
+ else if (codepoint < 0x10000) {
+ destination->used += 3;
+ }
+ else {
+ destination->used += 4;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (source.string[i] == f_string_ascii_minus_s.string[0]) {
+
+ // The "\U-" designates the termination of a Unicode sequence.
+ }
+ else {
+
+ // No plus found, so only the "\U" is considered invalid.
+ // This character is to be printed.
+ --i;
+ }
+ }
+ }
+ else {
+ status = f_string_dynamic_increase_by(F_memory_default_allocation_small_d, destination);
+ if (F_status_is_error(status)) return status;
+
+ destination->string[destination->used++] = source.string[i];
+ }
+ } // for
+
+ if (F_status_is_error(status)) return status;
+
+ return F_none;
+ }
+#endif // _di_fake_make_operate_process_buffer_escape_
+
#ifndef _di_fake_make_operate_process_execute_
f_status_t fake_make_operate_process_execute(fake_make_data_t * const data_make, const f_string_static_t program, const f_string_statics_t arguments, const bool as_shell) {
#endif // _di_fake_make_operate_process_
/**
+ * Process the given string, converting escape sequences into code.
+ *
+ * The following escape sequences are supported for printing special characters:
+ * - "\f": Form Feed.
+ * - "\n": New Line.
+ * - "\r": Carriage Return.
+ * - "\t": Tab.
+ * - "\v": Vertical Tab.
+ * - "\\": Backslash Character (may require additional slashes in certain circumstances.)
+ * - "\0": NULL Character.
+ * - "\U+": Unicode Sequence (followed by a valid Unicode sequence with a minimum 4 hexidecimal digits and a maximum of 6 hexidecimal digits).
+ * - "\U-": Terminate a Unicode Sequence, allowing for "\U+000A\U-5" to be equivalent to "\n5".
+ *
+ * If the Unicode is invalid, then nothing is copied for that character (the invalid character is skipped when printing).
+ * Example Unicodes\:
+ * - "\U+000A": Prints a new line, equivalent to "\n".
+ * - "\U+2E19": Prints the Unicode feather-like character "⸙".
+ *
+ * Only ASCII alpha-numeric hexidecimal digits are allowed in the Unicode sequence (upper or lower case).
+ *
+ * Invalid or unknown escape sequences are not copied.
+ *
+ * @param data_make
+ * All make related setting data, including data from the fakefile and the build settings file.
+ * @param source
+ * The source string to process and esacpe.
+ * @param destination
+ * The processed and escaped string.
+ *
+ * @return
+ * F_none on success.
+ * F_data_not if source.used is 0.
+ *
+ * Errors (with error bit) from: f_string_dynamic_increase_by().
+ * Errors (with error bit) from: f_utf_unicode_from().
+ * Errors (with error bit) from: f_utf_unicode_string_to().
+ *
+ * @see f_string_dynamic_increase_by()
+ * @see f_utf_unicode_from()
+ * @see f_utf_unicode_string_to()
+ */
+#ifndef _di_fake_make_operate_process_buffer_escape_
+ extern f_status_t fake_make_operate_process_buffer_escape(fake_make_data_t * const data_make, const f_string_static_t source, f_string_dynamic_t * const destination) F_attribute_visibility_internal_d;
+#endif // _di_fake_make_operate_process_buffer_escape_
+
+/**
* Execute either the run operation or the shell operation.
*
* @param data_make
#include "private-fake.h"
#include "private-clean.h"
#include "private-make.h"
+#include "private-make-operate_process.h"
#include "private-make-operate_process_type.h"
#include "private-print.h"
}
#endif // _di_fake_make_operate_process_type_touch_
+#ifndef _di_fake_make_operate_process_type_write_
+ f_status_t fake_make_operate_process_type_write(fake_make_data_t * const data_make, const f_string_dynamics_t arguments) {
+
+ f_status_t status = F_none;
+ f_file_t file = f_file_t_initialize;
+
+ status = f_file_exists(arguments.array[0], F_true);
+
+ if (arguments.used == 1 || status == F_false) {
+ status = f_file_stream_open(arguments.array[0], f_file_open_mode_truncate_s, &file);
+
+ if (F_status_is_error(status)) {
+ if (F_status_is_error_not(fll_path_canonical(arguments.array[0], &data_make->path_cache))) {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "f_file_stream_open", F_true, data_make->path_cache, f_file_operation_open_s, fll_error_file_type_file_e);
+ }
+ else {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "f_file_stream_open", F_true, arguments.array[0], f_file_operation_open_s, fll_error_file_type_file_e);
+ }
+ }
+
+ if (F_status_is_error_not(status)) {
+
+ // Keep the stream open if there is a string to write to it.
+ if (arguments.used > 1) {
+ status = F_false;
+ }
+ else {
+ f_file_stream_close(F_true, &file);
+ }
+ }
+ }
+
+ if (F_status_is_error_not(status) && arguments.used > 1) {
+ if (status != F_false) {
+ status = f_file_stream_open(arguments.array[0], f_file_open_mode_append_s, &file);
+
+ if (F_status_is_error(status)) {
+ if (F_status_is_error_not(fll_path_canonical(arguments.array[0], &data_make->path_cache))) {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "f_file_stream_open", F_true, data_make->path_cache, f_file_operation_open_s, fll_error_file_type_file_e);
+ }
+ else {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "f_file_stream_open", F_true, arguments.array[0], f_file_operation_open_s, fll_error_file_type_file_e);
+ }
+ }
+ }
+
+ if (F_status_is_error_not(status)) {
+ for (f_array_length_t i = 1; i < arguments.used; ++i) {
+
+ status = fake_make_operate_process_buffer_escape(data_make, arguments.array[i], &data_make->cache_1);
+
+ if (F_status_is_error(status)) {
+ if (F_status_is_error_not(fll_path_canonical(arguments.array[0], &data_make->path_cache))) {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "fake_make_operate_process_buffer_escape", F_true, data_make->path_cache, f_file_operation_write_s, fll_error_file_type_file_e);
+ }
+ else {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "fake_make_operate_process_buffer_escape", F_true, arguments.array[0], f_file_operation_write_s, fll_error_file_type_file_e);
+ }
+
+ break;
+ }
+
+ status = f_file_stream_write(file, data_make->cache_1, 0);
+
+ if (F_status_is_error(status)) {
+ if (F_status_is_error_not(fll_path_canonical(arguments.array[0], &data_make->path_cache))) {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "f_file_stream_write", F_true, data_make->path_cache, f_file_operation_write_s, fll_error_file_type_file_e);
+ }
+ else {
+ fll_error_file_print(data_make->error, F_status_set_fine(status), "f_file_stream_write", F_true, arguments.array[0], f_file_operation_write_s, fll_error_file_type_file_e);
+ }
+
+ break;
+ }
+
+ if (i + 1 < arguments.used) {
+ status = f_file_stream_write(file, f_string_ascii_space_s, 0);
+ }
+ } // for
+ }
+
+ f_file_stream_close(F_true, &file);
+ }
+
+ if (F_status_is_error(status)) return status;
+
+ return F_none;
+ }
+#endif // _di_fake_make_operate_process_type_write_
+
#ifdef __cplusplus
} // extern "C"
#endif
extern f_status_t fake_make_operate_process_type_touch(fake_make_data_t * const data_make, const f_string_dynamics_t arguments) F_attribute_visibility_internal_d;
#endif // _di_fake_make_operate_process_type_touch_
+/**
+ * Perform the write operation process.
+ *
+ * @param data_make
+ * All make related setting data, including data from the fakefile and the build settings file.
+ * This resets and uses data_make.cache_1.
+ * @param arguments
+ * The arguments for the run or shell operation.
+ *
+ * @return
+ * F_none on success.
+ *
+ * Errors (with error bit) from: f_file_exists().
+ * Errors (with error bit) from: f_file_stream_open().
+ * Errors (with error bit) from: f_file_stream_write().
+ *
+ * @see f_file_exists()
+ * @see f_file_stream_open()
+ * @see f_file_stream_write()
+ */
+#ifndef _di_fake_make_operate_process_type_write_
+ extern f_status_t fake_make_operate_process_type_write(fake_make_data_t * const data_make, const f_string_dynamics_t arguments) F_attribute_visibility_internal_d;
+#endif // _di_fake_make_operate_process_type_write_
+
#ifdef __cplusplus
} // extern "C"
#endif
}
}
+ if (state_process->operation == fake_make_operation_type_write_e) {
+ if (arguments.used) {
+ if (!arguments.array[0].used) {
+ fake_print_error_argument_empty(data_make, 1);
+
+ *status = F_status_set_error(F_failure);
+ }
+ else {
+ *status = fake_make_assure_inside_project(data_make, arguments.array[0]);
+
+ if (F_status_is_error(*status)) {
+ fake_print_message_section_operation_path_outside(data_make->data, data_make->error, F_status_set_fine(*status), "fake_make_assure_inside_project", data_make->path_cache.used ? data_make->path_cache : arguments.array[0]);
+
+ if (F_status_set_fine(*status) == F_false) {
+ *status = F_status_set_error(F_failure);
+ }
+ }
+ }
+ }
+ else {
+ fake_print_error_requires_more_arguments(data_make);
+
+ *status = F_status_set_error(F_failure);
+ }
+ }
+
// Note: there is nothing to validate for fake_make_operation_type_print_e.
}
#endif // _di_fake_make_operate_validate_
}
#endif // _di_fake_print_error_too_many_arguments_
+#ifndef _di_fake_print_error_argument_empty_
+ void fake_print_error_argument_empty(fake_make_data_t * const data_make, const f_array_length_t index) {
+
+ if (data_make->error.verbosity == f_console_verbosity_quiet_e) return;
+ if (!data_make->error.to.stream) return;
+
+ flockfile(data_make->error.to.stream);
+
+ fl_print_format("%r%[%QThe %]", data_make->error.to.stream, f_string_eol_s, data_make->error.context, data_make->error.prefix, data_make->error.context);
+ fl_print_format("%[%un%]", data_make->error.to.stream, data_make->error.notable, index, data_make->error.notable);
+ fl_print_format("%[ argument must not be an empty string.%]%r", data_make->error.to.stream, data_make->error.context, data_make->error.context, f_string_eol_s);
+
+ funlockfile(data_make->error.to.stream);
+ }
+#endif // _di_fake_print_error_argument_empty_
+
#ifndef _di_fake_print_message_section_operation_failed_
void fake_print_message_section_operation_failed(fake_data_t * const data, const fl_print_t print, const f_string_static_t buffer, const f_string_range_t section_name, const f_string_range_t operation_name) {
#endif // _di_fake_print_error_too_many_arguments_
/**
+ * Print an error message for when an argument is an empty string.
+ *
+ * @param data_make
+ * All make related setting data, including data from the fakefile and the build settings file.
+ * @param index
+ * The index of the argument that is an empty string.
+ *
+ * @see fll_print_format()
+ */
+#ifndef _di_fake_print_error_argument_empty_
+ extern void fake_print_error_argument_empty(fake_make_data_t * const data_make, const f_array_length_t index) F_attribute_visibility_internal_d;
+#endif // _di_fake_print_error_argument_empty_
+
+/**
* Print error messages when processing some fakefile section, for a specific line and operation, and that operation failed.
*
* @param data
The first Content must be either "file" or "directory".
The remaining Content must be a path to the file.
+ - write\:
+ Write strings to a file within the project root.
+ The Content after the first Content is appended to the file.
+
+ The first Content represents the file to write to.
+ If there is no Content beyond the first, then the file is truncated (all content within the file is deleted).
+
+ In all cases, if the file does not exist, the file is created.
+
+ When only the first Content exists, this acts similar to the "touch" operation.
+ The major difference between the two is that the "touch" operation does not alter the content within the file.
+ This does alter the content within the file.
+
+ A single space is printed between each argument.
+ To preserve spaces, wrap the message in quotes (single or double).
+
+ The following escape sequences are supported for printing special characters:
+ - "\f": Form Feed.
+ - "\n": New Line.
+ - "\r": Carriage Return.
+ - "\t": Tab.
+ - "\v": Vertical Tab.
+ - "\\": Backslash Character (may require additional slashes in certain circumstances.)
+ - "\0": NULL Character.
+ - "\U+": Unicode Sequence (followed by a valid Unicode sequence with a minimum 4 hexidecimal digits and a maximum of 6 hexidecimal digits).
+ - "\U-": Terminate a Unicode Sequence, allowing for "\U+000A\U-5" to be equivalent to "\n5".
+
+ If the Unicode is invalid, then nothing is printed for that character (the invalid character is skipped when printing).
+ Example Unicodes\:
+ - "\U+000A": Prints a new line, equivalent to "\n".
+ - "\U+2E19": Prints the Unicode feather-like character "⸙".
+
+ Only ASCII alpha-numeric hexidecimal digits are allowed in the Unicode sequence (upper or lower case).
+
+ Invalid or unknown escape sequences are not printed.
+
The IKI vocabulary context is supported and is further clarified as follows\:
- context\:
The context Object represents a name representing simple context or complex combination of context.
- skeleton: Zero Content.
- to: One Content. First Content is the directory path.
- top: Zero Content.
- - touch: Two or more Content. First content is one of "file" or "directory", remaining Content are paths to files.
+ - touch: Two or more Content. First Content is one of "file" or "directory", remaining Content are paths to files.
+ - write: One or more Content. First Content the file to write to, remaining Content represent the string to write.
The "if" Section Operation conditions are\:
- ==: Two or more Content.