diff --git a/ChangeLog.md b/ChangeLog.md index 3d42433..81135bc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,8 @@ ### API - Add functions to check if table is empty to the API. +- `ft_ln` returns status of operation. +- Add new table property `adding_strategy` (2 strategies available - replace(default) and insert). ## v0.3.2 diff --git a/lib/fort.c b/lib/fort.c index da5455d..a48c39d 100644 --- a/lib/fort.c +++ b/lib/fort.c @@ -311,6 +311,12 @@ size_t vector_capacity(const f_vector_t *); FT_INTERNAL int vector_push(f_vector_t *, const void *item); +FT_INTERNAL +int vector_insert(f_vector_t *, const void *item, size_t pos); + +FT_INTERNAL +f_vector_t *vector_split(f_vector_t *, size_t pos); + FT_INTERNAL const void *vector_at_c(const f_vector_t *vector, size_t index); @@ -320,12 +326,15 @@ void *vector_at(f_vector_t *, size_t index); FT_INTERNAL f_status vector_swap(f_vector_t *cur_vec, f_vector_t *mv_vec, size_t pos); +FT_INTERNAL +void vector_clear(f_vector_t *); + +FT_INTERNAL +int vector_erase(f_vector_t *, size_t index); #ifdef FT_TEST_BUILD f_vector_t *copy_vector(f_vector_t *); size_t vector_index_of(const f_vector_t *, const void *item); -int vector_erase(f_vector_t *, size_t index); -void vector_clear(f_vector_t *); #endif #endif /* VECTOR_H */ @@ -2020,6 +2029,7 @@ struct fort_entire_table_properties { unsigned int top_margin; unsigned int right_margin; unsigned int bottom_margin; + enum ft_adding_strategy add_strategy; }; typedef struct fort_entire_table_properties fort_entire_table_properties_t; extern fort_entire_table_properties_t g_entire_table_properties; @@ -2134,6 +2144,9 @@ void destroy_row(f_row_t *row); FT_INTERNAL f_row_t *copy_row(f_row_t *row); +FT_INTERNAL +f_row_t *split_row(f_row_t *row, size_t pos); + FT_INTERNAL f_row_t *create_row_from_string(const char *str); @@ -2152,9 +2165,15 @@ const f_cell_t *get_cell_c(const f_row_t *row, size_t col); FT_INTERNAL f_cell_t *get_cell_and_create_if_not_exists(f_row_t *row, size_t col); +FT_INTERNAL +f_cell_t *create_cell_in_position(f_row_t *row, size_t col); + FT_INTERNAL f_status swap_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos); +FT_INTERNAL +f_status insert_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos); + FT_INTERNAL size_t group_cell_number(const f_row_t *row, size_t master_cell_col); @@ -2611,7 +2630,14 @@ ft_table_t *ft_create_table(void) F_FREE(result); return NULL; } - result->properties = NULL; + + result->properties = create_table_properties(); + if (result->properties == NULL) { + destroy_vector(result->separators); + destroy_vector(result->rows); + F_FREE(result); + return NULL; + } result->conv_buffer = NULL; result->cur_row = 0; result->cur_col = 0; @@ -2677,7 +2703,12 @@ ft_table_t *ft_copy_table(ft_table_t *table) vector_push(result->separators, &new_sep); } - + /* note: by default new table has allocated default properties, so we + * have to destroy them first. + */ + if (result->properties) { + destroy_table_properties(result->properties); + } result->properties = copy_table_properties(table->properties); if (result->properties == NULL) { ft_destroy_table(result); @@ -2691,12 +2722,57 @@ ft_table_t *ft_copy_table(ft_table_t *table) return result; } +static int split_cur_row(ft_table_t *table, f_row_t **tail_of_cur_row) +{ + if (table->cur_row >= vector_size(table->rows)) { + tail_of_cur_row = NULL; + return 0; + } -void ft_ln(ft_table_t *table) + f_row_t *row = *(f_row_t **)vector_at(table->rows, table->cur_row); + if (table->cur_col >= columns_in_row(row)) { + tail_of_cur_row = NULL; + return 0; + } + + f_row_t *tail = split_row(row, table->cur_col); + if (!tail) { + tail_of_cur_row = NULL; + return FT_ERROR; + } + + *tail_of_cur_row = tail; + return 0; +} + +int ft_ln(ft_table_t *table) { assert(table); + fort_entire_table_properties_t *table_props = &table->properties->entire_table_properties; + switch (table_props->add_strategy) { + case FT_STRATEGY_INSERT: { + f_row_t *new_row = NULL; + if (FT_IS_ERROR(split_cur_row(table, &new_row))) { + return FT_ERROR; + } + if (new_row) { + if (FT_IS_ERROR(vector_insert(table->rows, &new_row, table->cur_row + 1))) { + destroy_row(new_row); + return FT_ERROR; + } + } + break; + } + case FT_STRATEGY_REPLACE: + // do nothing + break; + default: + assert(0 && "Unexpected situation inside libfort"); + break; + } table->cur_col = 0; table->cur_row++; + return FT_SUCCESS; } size_t ft_cur_row(const ft_table_t *table) @@ -2760,7 +2836,22 @@ static int ft_row_printf_impl_(ft_table_t *table, size_t row, const struct f_str new_cols = columns_in_row(new_row); cur_row_p = (f_row_t **)vector_at(table->rows, row); - swap_row(*cur_row_p, new_row, table->cur_col); + + switch (table->properties->entire_table_properties.add_strategy) { + case FT_STRATEGY_INSERT: { + if (FT_IS_ERROR(insert_row(*cur_row_p, new_row, table->cur_col))) + goto clear; + break; + } + case FT_STRATEGY_REPLACE: { + if (FT_IS_ERROR(swap_row(*cur_row_p, new_row, table->cur_col))) + goto clear; + break; + } + default: + assert(0 && "Unexpected situation inside libfort"); + break; + } table->cur_col += new_cols; destroy_row(new_row); @@ -4920,6 +5011,7 @@ fort_entire_table_properties_t g_entire_table_properties = { 0, /* top_margin */ 0, /* right_margin */ 0, /* bottom_margin */ + FT_STRATEGY_REPLACE, /* add_strategy */ }; static f_status set_entire_table_property_internal(fort_entire_table_properties_t *properties, uint32_t property, int value) @@ -4934,6 +5026,8 @@ static f_status set_entire_table_property_internal(fort_entire_table_properties_ properties->right_margin = value; } else if (PROP_IS_SET(property, FT_TPROP_BOTTOM_MARGIN)) { properties->bottom_margin = value; + } else if (PROP_IS_SET(property, FT_TPROP_ADDING_STRATEGY)) { + properties->add_strategy = (enum ft_adding_strategy)value; } else { return FT_EINVAL; } @@ -4989,7 +5083,8 @@ f_table_properties_t g_table_properties = { 0, /* left_margin */ 0, /* top_margin */ 0, /* right_margin */ - 0 /* bottom_margin */ + 0, /* bottom_margin */ + FT_STRATEGY_REPLACE, /* add_strategy */ } }; @@ -5084,21 +5179,41 @@ struct f_row { f_vector_t *cells; }; - -FT_INTERNAL -f_row_t *create_row(void) +static +f_row_t *create_row_impl(f_vector_t *cells) { f_row_t *row = (f_row_t *)F_CALLOC(1, sizeof(f_row_t)); if (row == NULL) return NULL; - row->cells = create_vector(sizeof(f_cell_t *), DEFAULT_VECTOR_CAPACITY); - if (row->cells == NULL) { - F_FREE(row); - return NULL; + if (cells) { + row->cells = cells; + } else { + row->cells = create_vector(sizeof(f_cell_t *), DEFAULT_VECTOR_CAPACITY); + if (row->cells == NULL) { + F_FREE(row); + return NULL; + } } return row; } +FT_INTERNAL +f_row_t *create_row(void) +{ + return create_row_impl(NULL); +} + +static +void destroy_each_cell(f_vector_t *cells) +{ + size_t i = 0; + size_t cells_n = vector_size(cells); + for (i = 0; i < cells_n; ++i) { + f_cell_t *cell = *(f_cell_t **)vector_at(cells, i); + destroy_cell(cell); + } +} + FT_INTERNAL void destroy_row(f_row_t *row) { @@ -5106,12 +5221,7 @@ void destroy_row(f_row_t *row) return; if (row->cells) { - size_t i = 0; - size_t cells_n = vector_size(row->cells); - for (i = 0; i < cells_n; ++i) { - f_cell_t *cell = *(f_cell_t **)vector_at(row->cells, i); - destroy_cell(cell); - } + destroy_each_cell(row->cells); destroy_vector(row->cells); } @@ -5141,6 +5251,23 @@ f_row_t *copy_row(f_row_t *row) return result; } +FT_INTERNAL +f_row_t *split_row(f_row_t *row, size_t pos) +{ + assert(row); + + f_vector_t *cells = vector_split(row->cells, pos); + if (!cells) + return NULL; + f_row_t *tail = create_row_impl(cells); + if (!tail) { + destroy_each_cell(cells); + destroy_vector(cells); + } + return tail; +} + + FT_INTERNAL size_t columns_in_row(const f_row_t *row) { @@ -5202,6 +5329,23 @@ f_cell_t *get_cell_and_create_if_not_exists(f_row_t *row, size_t col) return get_cell_impl(row, col, CREATE_ON_NULL); } +FT_INTERNAL +f_cell_t *create_cell_in_position(f_row_t *row, size_t col) +{ + if (row == NULL || row->cells == NULL) { + return NULL; + } + + f_cell_t *new_cell = create_cell(); + if (new_cell == NULL) + return NULL; + if (FT_IS_ERROR(vector_insert(row->cells, &new_cell, col))) { + destroy_cell(new_cell); + return NULL; + } + return *(f_cell_t **)vector_at(row->cells, col); +} + FT_INTERNAL f_status swap_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos) @@ -5220,6 +5364,37 @@ f_status swap_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos) return vector_swap(cur_row->cells, ins_row->cells, pos); } +/* Ownership of cells of `ins_row` is passed to `cur_row`. */ +FT_INTERNAL +f_status insert_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos) +{ + assert(cur_row); + assert(ins_row); + + while (vector_size(cur_row->cells) < pos) { + f_cell_t *new_cell = create_cell(); + if (!new_cell) + return FT_ERROR; + vector_push(cur_row->cells, &new_cell); + } + + size_t sz = vector_size(ins_row->cells); + size_t i = 0; + for (i = 0; i < sz; ++i) { + f_cell_t *cell = *(f_cell_t **)vector_at(ins_row->cells, i); + if (FT_IS_ERROR(vector_insert(cur_row->cells, &cell, pos + i))) { + /* clean up what we have inserted */ + while (i--) { + vector_erase(cur_row->cells, pos); + } + return FT_ERROR; + } + } + /* Clear cells so that it will be safe to destroy this row */ + vector_clear(ins_row->cells); + return FT_SUCCESS; +} + FT_INTERNAL size_t group_cell_number(const f_row_t *row, size_t master_cell_col) @@ -6672,7 +6847,6 @@ f_row_t *get_row_and_create_if_not_exists(ft_table_t *table, size_t row) return get_row_impl(table, row, CREATE_ON_NULL); } - FT_INTERNAL f_string_buffer_t *get_cur_str_buffer_and_create_if_not_exists(ft_table_t *table) { @@ -6681,7 +6855,21 @@ f_string_buffer_t *get_cur_str_buffer_and_create_if_not_exists(ft_table_t *table f_row_t *row = get_row_and_create_if_not_exists(table, table->cur_row); if (row == NULL) return NULL; - f_cell_t *cell = get_cell_and_create_if_not_exists(row, table->cur_col); + + f_cell_t *cell = NULL; + fort_entire_table_properties_t *table_props = &table->properties->entire_table_properties; + switch (table_props->add_strategy) { + case FT_STRATEGY_INSERT: + cell = create_cell_in_position(row, table->cur_col); + break; + case FT_STRATEGY_REPLACE: + cell = get_cell_and_create_if_not_exists(row, table->cur_col); + break; + default: + assert(0 && "Unexpected situation inside libfort"); + break; + } + if (cell == NULL) return NULL; @@ -6983,6 +7171,56 @@ int vector_push(f_vector_t *vector, const void *item) return FT_SUCCESS; } +FT_INTERNAL +int vector_insert(f_vector_t *vector, const void *item, size_t pos) +{ + assert(vector); + assert(item); + size_t needed_capacity = MAX(pos + 1, vector->m_size + 1); + if (vector->m_capacity < needed_capacity) { + if (vector_reallocate_(vector, needed_capacity) == -1) + return FT_ERROR; + vector->m_capacity = needed_capacity; + } + size_t offset = pos * vector->m_item_size; + if (pos >= vector->m_size) { + /* Data in the middle are not initialized */ + memcpy((char *)vector->m_data + offset, item, vector->m_item_size); + vector->m_size = pos + 1; + return FT_SUCCESS; + } else { + /* Shift following data by one position */ + memmove((char *)vector->m_data + offset + vector->m_item_size, + (char *)vector->m_data + offset, + vector->m_item_size * (vector->m_size - pos)); + memcpy((char *)vector->m_data + offset, item, vector->m_item_size); + ++(vector->m_size); + return FT_SUCCESS; + } +} + +FT_INTERNAL +f_vector_t *vector_split(f_vector_t *vector, size_t pos) +{ + size_t trailing_sz = vector->m_size > pos ? vector->m_size - pos : 0; + f_vector_t *new_vector = create_vector(vector->m_item_size, trailing_sz); + if (!new_vector) + return new_vector; + if (new_vector->m_capacity < trailing_sz) { + destroy_vector(new_vector); + return NULL; + } + + if (trailing_sz == 0) + return new_vector; + + size_t offset = vector->m_item_size * pos; + memcpy(new_vector->m_data, (char *)vector->m_data + offset, + trailing_sz * vector->m_item_size); + new_vector->m_size = trailing_sz; + vector->m_size = pos; + return new_vector; +} FT_INTERNAL const void *vector_at_c(const f_vector_t *vector, size_t index) @@ -7058,6 +7296,26 @@ f_status vector_swap(f_vector_t *cur_vec, f_vector_t *mv_vec, size_t pos) return FT_SUCCESS; } +FT_INTERNAL +void vector_clear(f_vector_t *vector) +{ + vector->m_size = 0; +} + +FT_INTERNAL +int vector_erase(f_vector_t *vector, size_t index) +{ + assert(vector); + + if (vector->m_size == 0 || index >= vector->m_size) + return FT_ERROR; + + memmove((char *)vector->m_data + vector->m_item_size * index, + (char *)vector->m_data + vector->m_item_size * (index + 1), + (vector->m_size - 1 - index) * vector->m_item_size); + vector->m_size--; + return FT_SUCCESS; +} #ifdef FT_TEST_BUILD @@ -7091,26 +7349,6 @@ size_t vector_index_of(const f_vector_t *vector, const void *item) return INVALID_VEC_INDEX; } - -int vector_erase(f_vector_t *vector, size_t index) -{ - assert(vector); - - if (vector->m_size == 0 || index >= vector->m_size) - return FT_ERROR; - - memmove((char *)vector->m_data + vector->m_item_size * index, - (char *)vector->m_data + vector->m_item_size * (index + 1), - (vector->m_size - 1 - index) * vector->m_item_size); - vector->m_size--; - return FT_SUCCESS; -} - - -void vector_clear(f_vector_t *vector) -{ - vector->m_size = 0; -} #endif /******************************************************** diff --git a/lib/fort.h b/lib/fort.h index c7147fb..132086f 100644 --- a/lib/fort.h +++ b/lib/fort.h @@ -275,8 +275,14 @@ ft_table_t *ft_copy_table(ft_table_t *table); * * @param table * Pointer to formatted table. + * @return + * - 0: Success; data were written + * - (<0): In case of error. + * @note + * This function can fail only in case FT_STRATEGY_INSERT adding strategy + * was set for the table. */ -void ft_ln(ft_table_t *table); +int ft_ln(ft_table_t *table); /** * Get row number of the current cell. @@ -812,12 +818,23 @@ int ft_set_cell_prop(ft_table_t *table, size_t row, size_t col, uint32_t propert * @name Table properties identifiers. * @{ */ -#define FT_TPROP_LEFT_MARGIN (0x01U << 0) -#define FT_TPROP_TOP_MARGIN (0x01U << 1) -#define FT_TPROP_RIGHT_MARGIN (0x01U << 2) -#define FT_TPROP_BOTTOM_MARGIN (0x01U << 3) +#define FT_TPROP_LEFT_MARGIN (0x01U << 0) +#define FT_TPROP_TOP_MARGIN (0x01U << 1) +#define FT_TPROP_RIGHT_MARGIN (0x01U << 2) +#define FT_TPROP_BOTTOM_MARGIN (0x01U << 3) +#define FT_TPROP_ADDING_STRATEGY (0x01U << 4) /** @} */ +/** + * Adding strategy. + * + * Determines what happens with old content if current cell is not empty after + * adding data to it. Default strategy is FT_STRATEGY_REPLACE. + */ +enum ft_adding_strategy { + FT_STRATEGY_REPLACE = 0, /**< Replace old content. */ + FT_STRATEGY_INSERT /**< Insert new conten. Old content is shifted. */ +}; /** diff --git a/lib/fort.hpp b/lib/fort.hpp index 9d223ce..fe0ffb5 100644 --- a/lib/fort.hpp +++ b/lib/fort.hpp @@ -60,6 +60,17 @@ enum class row_type { header = FT_ROW_HEADER }; +/** + * Adding strategy. + * + * Determines what happens with old content if current cell is not empty after + * adding data to it. Default strategy is 'replace'. + */ +enum class add_strategy { + replace = FT_STRATEGY_REPLACE, + insert = FT_STRATEGY_INSERT +}; + /** * Colors. */ @@ -893,6 +904,22 @@ public: { return FT_IS_SUCCESS(ft_set_tbl_prop(table_, FT_TPROP_BOTTOM_MARGIN, value)); } + + /** + * Set table adding strategy. + * + * @param value + * Adding strategy. + * @return + * - true: Success; table property was changed. + * - false: In case of error. + */ + bool set_adding_strategy(fort::add_strategy value) + { + return FT_IS_SUCCESS(ft_set_tbl_prop(table_, + FT_TPROP_ADDING_STRATEGY, + static_cast(value))); + } private: ft_table_t *table_; mutable std::stringstream stream_; diff --git a/src/fort.h b/src/fort.h index c7147fb..132086f 100644 --- a/src/fort.h +++ b/src/fort.h @@ -275,8 +275,14 @@ ft_table_t *ft_copy_table(ft_table_t *table); * * @param table * Pointer to formatted table. + * @return + * - 0: Success; data were written + * - (<0): In case of error. + * @note + * This function can fail only in case FT_STRATEGY_INSERT adding strategy + * was set for the table. */ -void ft_ln(ft_table_t *table); +int ft_ln(ft_table_t *table); /** * Get row number of the current cell. @@ -812,12 +818,23 @@ int ft_set_cell_prop(ft_table_t *table, size_t row, size_t col, uint32_t propert * @name Table properties identifiers. * @{ */ -#define FT_TPROP_LEFT_MARGIN (0x01U << 0) -#define FT_TPROP_TOP_MARGIN (0x01U << 1) -#define FT_TPROP_RIGHT_MARGIN (0x01U << 2) -#define FT_TPROP_BOTTOM_MARGIN (0x01U << 3) +#define FT_TPROP_LEFT_MARGIN (0x01U << 0) +#define FT_TPROP_TOP_MARGIN (0x01U << 1) +#define FT_TPROP_RIGHT_MARGIN (0x01U << 2) +#define FT_TPROP_BOTTOM_MARGIN (0x01U << 3) +#define FT_TPROP_ADDING_STRATEGY (0x01U << 4) /** @} */ +/** + * Adding strategy. + * + * Determines what happens with old content if current cell is not empty after + * adding data to it. Default strategy is FT_STRATEGY_REPLACE. + */ +enum ft_adding_strategy { + FT_STRATEGY_REPLACE = 0, /**< Replace old content. */ + FT_STRATEGY_INSERT /**< Insert new conten. Old content is shifted. */ +}; /** diff --git a/src/fort.hpp b/src/fort.hpp index 9d223ce..fe0ffb5 100644 --- a/src/fort.hpp +++ b/src/fort.hpp @@ -60,6 +60,17 @@ enum class row_type { header = FT_ROW_HEADER }; +/** + * Adding strategy. + * + * Determines what happens with old content if current cell is not empty after + * adding data to it. Default strategy is 'replace'. + */ +enum class add_strategy { + replace = FT_STRATEGY_REPLACE, + insert = FT_STRATEGY_INSERT +}; + /** * Colors. */ @@ -893,6 +904,22 @@ public: { return FT_IS_SUCCESS(ft_set_tbl_prop(table_, FT_TPROP_BOTTOM_MARGIN, value)); } + + /** + * Set table adding strategy. + * + * @param value + * Adding strategy. + * @return + * - true: Success; table property was changed. + * - false: In case of error. + */ + bool set_adding_strategy(fort::add_strategy value) + { + return FT_IS_SUCCESS(ft_set_tbl_prop(table_, + FT_TPROP_ADDING_STRATEGY, + static_cast(value))); + } private: ft_table_t *table_; mutable std::stringstream stream_; diff --git a/src/fort_impl.c b/src/fort_impl.c index d007571..1c505ac 100644 --- a/src/fort_impl.c +++ b/src/fort_impl.c @@ -57,7 +57,14 @@ ft_table_t *ft_create_table(void) F_FREE(result); return NULL; } - result->properties = NULL; + + result->properties = create_table_properties(); + if (result->properties == NULL) { + destroy_vector(result->separators); + destroy_vector(result->rows); + F_FREE(result); + return NULL; + } result->conv_buffer = NULL; result->cur_row = 0; result->cur_col = 0; @@ -123,7 +130,12 @@ ft_table_t *ft_copy_table(ft_table_t *table) vector_push(result->separators, &new_sep); } - + /* note: by default new table has allocated default properties, so we + * have to destroy them first. + */ + if (result->properties) { + destroy_table_properties(result->properties); + } result->properties = copy_table_properties(table->properties); if (result->properties == NULL) { ft_destroy_table(result); @@ -137,12 +149,57 @@ ft_table_t *ft_copy_table(ft_table_t *table) return result; } +static int split_cur_row(ft_table_t *table, f_row_t **tail_of_cur_row) +{ + if (table->cur_row >= vector_size(table->rows)) { + tail_of_cur_row = NULL; + return 0; + } -void ft_ln(ft_table_t *table) + f_row_t *row = *(f_row_t **)vector_at(table->rows, table->cur_row); + if (table->cur_col >= columns_in_row(row)) { + tail_of_cur_row = NULL; + return 0; + } + + f_row_t *tail = split_row(row, table->cur_col); + if (!tail) { + tail_of_cur_row = NULL; + return FT_ERROR; + } + + *tail_of_cur_row = tail; + return 0; +} + +int ft_ln(ft_table_t *table) { assert(table); + fort_entire_table_properties_t *table_props = &table->properties->entire_table_properties; + switch (table_props->add_strategy) { + case FT_STRATEGY_INSERT: { + f_row_t *new_row = NULL; + if (FT_IS_ERROR(split_cur_row(table, &new_row))) { + return FT_ERROR; + } + if (new_row) { + if (FT_IS_ERROR(vector_insert(table->rows, &new_row, table->cur_row + 1))) { + destroy_row(new_row); + return FT_ERROR; + } + } + break; + } + case FT_STRATEGY_REPLACE: + // do nothing + break; + default: + assert(0 && "Unexpected situation inside libfort"); + break; + } table->cur_col = 0; table->cur_row++; + return FT_SUCCESS; } size_t ft_cur_row(const ft_table_t *table) @@ -206,7 +263,22 @@ static int ft_row_printf_impl_(ft_table_t *table, size_t row, const struct f_str new_cols = columns_in_row(new_row); cur_row_p = (f_row_t **)vector_at(table->rows, row); - swap_row(*cur_row_p, new_row, table->cur_col); + + switch (table->properties->entire_table_properties.add_strategy) { + case FT_STRATEGY_INSERT: { + if (FT_IS_ERROR(insert_row(*cur_row_p, new_row, table->cur_col))) + goto clear; + break; + } + case FT_STRATEGY_REPLACE: { + if (FT_IS_ERROR(swap_row(*cur_row_p, new_row, table->cur_col))) + goto clear; + break; + } + default: + assert(0 && "Unexpected situation inside libfort"); + break; + } table->cur_col += new_cols; destroy_row(new_row); diff --git a/src/properties.c b/src/properties.c index 96ab525..3326c98 100644 --- a/src/properties.c +++ b/src/properties.c @@ -874,6 +874,7 @@ fort_entire_table_properties_t g_entire_table_properties = { 0, /* top_margin */ 0, /* right_margin */ 0, /* bottom_margin */ + FT_STRATEGY_REPLACE, /* add_strategy */ }; static f_status set_entire_table_property_internal(fort_entire_table_properties_t *properties, uint32_t property, int value) @@ -888,6 +889,8 @@ static f_status set_entire_table_property_internal(fort_entire_table_properties_ properties->right_margin = value; } else if (PROP_IS_SET(property, FT_TPROP_BOTTOM_MARGIN)) { properties->bottom_margin = value; + } else if (PROP_IS_SET(property, FT_TPROP_ADDING_STRATEGY)) { + properties->add_strategy = (enum ft_adding_strategy)value; } else { return FT_EINVAL; } @@ -943,7 +946,8 @@ f_table_properties_t g_table_properties = { 0, /* left_margin */ 0, /* top_margin */ 0, /* right_margin */ - 0 /* bottom_margin */ + 0, /* bottom_margin */ + FT_STRATEGY_REPLACE, /* add_strategy */ } }; diff --git a/src/properties.h b/src/properties.h index 48901c0..92365bf 100644 --- a/src/properties.h +++ b/src/properties.h @@ -177,6 +177,7 @@ struct fort_entire_table_properties { unsigned int top_margin; unsigned int right_margin; unsigned int bottom_margin; + enum ft_adding_strategy add_strategy; }; typedef struct fort_entire_table_properties fort_entire_table_properties_t; extern fort_entire_table_properties_t g_entire_table_properties; diff --git a/src/row.c b/src/row.c index 84987c5..4f45329 100644 --- a/src/row.c +++ b/src/row.c @@ -10,21 +10,41 @@ struct f_row { f_vector_t *cells; }; - -FT_INTERNAL -f_row_t *create_row(void) +static +f_row_t *create_row_impl(f_vector_t *cells) { f_row_t *row = (f_row_t *)F_CALLOC(1, sizeof(f_row_t)); if (row == NULL) return NULL; - row->cells = create_vector(sizeof(f_cell_t *), DEFAULT_VECTOR_CAPACITY); - if (row->cells == NULL) { - F_FREE(row); - return NULL; + if (cells) { + row->cells = cells; + } else { + row->cells = create_vector(sizeof(f_cell_t *), DEFAULT_VECTOR_CAPACITY); + if (row->cells == NULL) { + F_FREE(row); + return NULL; + } } return row; } +FT_INTERNAL +f_row_t *create_row(void) +{ + return create_row_impl(NULL); +} + +static +void destroy_each_cell(f_vector_t *cells) +{ + size_t i = 0; + size_t cells_n = vector_size(cells); + for (i = 0; i < cells_n; ++i) { + f_cell_t *cell = *(f_cell_t **)vector_at(cells, i); + destroy_cell(cell); + } +} + FT_INTERNAL void destroy_row(f_row_t *row) { @@ -32,12 +52,7 @@ void destroy_row(f_row_t *row) return; if (row->cells) { - size_t i = 0; - size_t cells_n = vector_size(row->cells); - for (i = 0; i < cells_n; ++i) { - f_cell_t *cell = *(f_cell_t **)vector_at(row->cells, i); - destroy_cell(cell); - } + destroy_each_cell(row->cells); destroy_vector(row->cells); } @@ -67,6 +82,23 @@ f_row_t *copy_row(f_row_t *row) return result; } +FT_INTERNAL +f_row_t *split_row(f_row_t *row, size_t pos) +{ + assert(row); + + f_vector_t *cells = vector_split(row->cells, pos); + if (!cells) + return NULL; + f_row_t *tail = create_row_impl(cells); + if (!tail) { + destroy_each_cell(cells); + destroy_vector(cells); + } + return tail; +} + + FT_INTERNAL size_t columns_in_row(const f_row_t *row) { @@ -128,6 +160,23 @@ f_cell_t *get_cell_and_create_if_not_exists(f_row_t *row, size_t col) return get_cell_impl(row, col, CREATE_ON_NULL); } +FT_INTERNAL +f_cell_t *create_cell_in_position(f_row_t *row, size_t col) +{ + if (row == NULL || row->cells == NULL) { + return NULL; + } + + f_cell_t *new_cell = create_cell(); + if (new_cell == NULL) + return NULL; + if (FT_IS_ERROR(vector_insert(row->cells, &new_cell, col))) { + destroy_cell(new_cell); + return NULL; + } + return *(f_cell_t **)vector_at(row->cells, col); +} + FT_INTERNAL f_status swap_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos) @@ -146,6 +195,37 @@ f_status swap_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos) return vector_swap(cur_row->cells, ins_row->cells, pos); } +/* Ownership of cells of `ins_row` is passed to `cur_row`. */ +FT_INTERNAL +f_status insert_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos) +{ + assert(cur_row); + assert(ins_row); + + while (vector_size(cur_row->cells) < pos) { + f_cell_t *new_cell = create_cell(); + if (!new_cell) + return FT_ERROR; + vector_push(cur_row->cells, &new_cell); + } + + size_t sz = vector_size(ins_row->cells); + size_t i = 0; + for (i = 0; i < sz; ++i) { + f_cell_t *cell = *(f_cell_t **)vector_at(ins_row->cells, i); + if (FT_IS_ERROR(vector_insert(cur_row->cells, &cell, pos + i))) { + /* clean up what we have inserted */ + while (i--) { + vector_erase(cur_row->cells, pos); + } + return FT_ERROR; + } + } + /* Clear cells so that it will be safe to destroy this row */ + vector_clear(ins_row->cells); + return FT_SUCCESS; +} + FT_INTERNAL size_t group_cell_number(const f_row_t *row, size_t master_cell_col) diff --git a/src/row.h b/src/row.h index 20f391d..ec45b19 100644 --- a/src/row.h +++ b/src/row.h @@ -18,6 +18,9 @@ void destroy_row(f_row_t *row); FT_INTERNAL f_row_t *copy_row(f_row_t *row); +FT_INTERNAL +f_row_t *split_row(f_row_t *row, size_t pos); + FT_INTERNAL f_row_t *create_row_from_string(const char *str); @@ -36,9 +39,15 @@ const f_cell_t *get_cell_c(const f_row_t *row, size_t col); FT_INTERNAL f_cell_t *get_cell_and_create_if_not_exists(f_row_t *row, size_t col); +FT_INTERNAL +f_cell_t *create_cell_in_position(f_row_t *row, size_t col); + FT_INTERNAL f_status swap_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos); +FT_INTERNAL +f_status insert_row(f_row_t *cur_row, f_row_t *ins_row, size_t pos); + FT_INTERNAL size_t group_cell_number(const f_row_t *row, size_t master_cell_col); diff --git a/src/table.c b/src/table.c index dc02017..f87e1ce 100644 --- a/src/table.c +++ b/src/table.c @@ -81,7 +81,6 @@ f_row_t *get_row_and_create_if_not_exists(ft_table_t *table, size_t row) return get_row_impl(table, row, CREATE_ON_NULL); } - FT_INTERNAL f_string_buffer_t *get_cur_str_buffer_and_create_if_not_exists(ft_table_t *table) { @@ -90,7 +89,21 @@ f_string_buffer_t *get_cur_str_buffer_and_create_if_not_exists(ft_table_t *table f_row_t *row = get_row_and_create_if_not_exists(table, table->cur_row); if (row == NULL) return NULL; - f_cell_t *cell = get_cell_and_create_if_not_exists(row, table->cur_col); + + f_cell_t *cell = NULL; + fort_entire_table_properties_t *table_props = &table->properties->entire_table_properties; + switch (table_props->add_strategy) { + case FT_STRATEGY_INSERT: + cell = create_cell_in_position(row, table->cur_col); + break; + case FT_STRATEGY_REPLACE: + cell = get_cell_and_create_if_not_exists(row, table->cur_col); + break; + default: + assert(0 && "Unexpected situation inside libfort"); + break; + } + if (cell == NULL) return NULL; diff --git a/src/vector.c b/src/vector.c index e12a42b..250c62f 100644 --- a/src/vector.c +++ b/src/vector.c @@ -91,6 +91,56 @@ int vector_push(f_vector_t *vector, const void *item) return FT_SUCCESS; } +FT_INTERNAL +int vector_insert(f_vector_t *vector, const void *item, size_t pos) +{ + assert(vector); + assert(item); + size_t needed_capacity = MAX(pos + 1, vector->m_size + 1); + if (vector->m_capacity < needed_capacity) { + if (vector_reallocate_(vector, needed_capacity) == -1) + return FT_ERROR; + vector->m_capacity = needed_capacity; + } + size_t offset = pos * vector->m_item_size; + if (pos >= vector->m_size) { + /* Data in the middle are not initialized */ + memcpy((char *)vector->m_data + offset, item, vector->m_item_size); + vector->m_size = pos + 1; + return FT_SUCCESS; + } else { + /* Shift following data by one position */ + memmove((char *)vector->m_data + offset + vector->m_item_size, + (char *)vector->m_data + offset, + vector->m_item_size * (vector->m_size - pos)); + memcpy((char *)vector->m_data + offset, item, vector->m_item_size); + ++(vector->m_size); + return FT_SUCCESS; + } +} + +FT_INTERNAL +f_vector_t *vector_split(f_vector_t *vector, size_t pos) +{ + size_t trailing_sz = vector->m_size > pos ? vector->m_size - pos : 0; + f_vector_t *new_vector = create_vector(vector->m_item_size, trailing_sz); + if (!new_vector) + return new_vector; + if (new_vector->m_capacity < trailing_sz) { + destroy_vector(new_vector); + return NULL; + } + + if (trailing_sz == 0) + return new_vector; + + size_t offset = vector->m_item_size * pos; + memcpy(new_vector->m_data, (char *)vector->m_data + offset, + trailing_sz * vector->m_item_size); + new_vector->m_size = trailing_sz; + vector->m_size = pos; + return new_vector; +} FT_INTERNAL const void *vector_at_c(const f_vector_t *vector, size_t index) @@ -166,6 +216,26 @@ f_status vector_swap(f_vector_t *cur_vec, f_vector_t *mv_vec, size_t pos) return FT_SUCCESS; } +FT_INTERNAL +void vector_clear(f_vector_t *vector) +{ + vector->m_size = 0; +} + +FT_INTERNAL +int vector_erase(f_vector_t *vector, size_t index) +{ + assert(vector); + + if (vector->m_size == 0 || index >= vector->m_size) + return FT_ERROR; + + memmove((char *)vector->m_data + vector->m_item_size * index, + (char *)vector->m_data + vector->m_item_size * (index + 1), + (vector->m_size - 1 - index) * vector->m_item_size); + vector->m_size--; + return FT_SUCCESS; +} #ifdef FT_TEST_BUILD @@ -199,24 +269,4 @@ size_t vector_index_of(const f_vector_t *vector, const void *item) return INVALID_VEC_INDEX; } - -int vector_erase(f_vector_t *vector, size_t index) -{ - assert(vector); - - if (vector->m_size == 0 || index >= vector->m_size) - return FT_ERROR; - - memmove((char *)vector->m_data + vector->m_item_size * index, - (char *)vector->m_data + vector->m_item_size * (index + 1), - (vector->m_size - 1 - index) * vector->m_item_size); - vector->m_size--; - return FT_SUCCESS; -} - - -void vector_clear(f_vector_t *vector) -{ - vector->m_size = 0; -} #endif diff --git a/src/vector.h b/src/vector.h index 715525c..a83c96d 100644 --- a/src/vector.h +++ b/src/vector.h @@ -21,6 +21,12 @@ size_t vector_capacity(const f_vector_t *); FT_INTERNAL int vector_push(f_vector_t *, const void *item); +FT_INTERNAL +int vector_insert(f_vector_t *, const void *item, size_t pos); + +FT_INTERNAL +f_vector_t *vector_split(f_vector_t *, size_t pos); + FT_INTERNAL const void *vector_at_c(const f_vector_t *vector, size_t index); @@ -30,12 +36,15 @@ void *vector_at(f_vector_t *, size_t index); FT_INTERNAL f_status vector_swap(f_vector_t *cur_vec, f_vector_t *mv_vec, size_t pos); +FT_INTERNAL +void vector_clear(f_vector_t *); + +FT_INTERNAL +int vector_erase(f_vector_t *, size_t index); #ifdef FT_TEST_BUILD f_vector_t *copy_vector(f_vector_t *); size_t vector_index_of(const f_vector_t *, const void *item); -int vector_erase(f_vector_t *, size_t index); -void vector_clear(f_vector_t *); #endif #endif /* VECTOR_H */ diff --git a/tests/bb_tests/test_table_basic.c b/tests/bb_tests/test_table_basic.c index 49c5387..86001b0 100644 --- a/tests/bb_tests/test_table_basic.c +++ b/tests/bb_tests/test_table_basic.c @@ -1608,8 +1608,95 @@ void test_table_write(void) } #endif + SCENARIO("Test write and printf functions simultaneously") { + table = ft_create_table(); + assert_true(table != NULL); + assert_true(FT_IS_SUCCESS(ft_write(table, "0", "1"))); + assert_true(FT_IS_SUCCESS(ft_printf(table, "2|3"))); + assert_true(FT_IS_SUCCESS(ft_write(table, "5", "6"))); + + const char *table_str = ft_to_string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+---+---+---+---+---+---+\n" + "| 0 | 1 | 2 | 3 | 5 | 6 |\n" + "+---+---+---+---+---+---+\n"; + assert_str_equal(table_str, table_str_etalon); + + ft_destroy_table(table); + } } +void test_table_insert_strategy(void) +{ + SCENARIO("Test ft_ln") { + ft_table_t *table = ft_create_table(); + assert_true(table != NULL); + ft_set_tbl_prop(table, FT_TPROP_ADDING_STRATEGY, FT_STRATEGY_INSERT); + assert_true(FT_IS_SUCCESS(ft_write_ln(table, "0", "1", "2"))); + ft_set_cur_cell(table, 0, 2); + assert_true(FT_IS_SUCCESS(ft_ln(table))); + ft_set_cur_cell(table, 1, 1); + assert_true(FT_IS_SUCCESS(ft_write(table, "3"))); + + const char *table_str = ft_to_string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+---+---+\n" + "| 0 | 1 |\n" + "| 2 | 3 |\n" + "+---+---+\n"; + assert_str_equal(table_str, table_str_etalon); + + ft_destroy_table(table); + } + + SCENARIO("Test write functions") { + ft_table_t *table = ft_create_table(); + assert_true(table != NULL); + ft_set_tbl_prop(table, FT_TPROP_ADDING_STRATEGY, FT_STRATEGY_INSERT); + assert_true(FT_IS_SUCCESS(ft_write_ln(table, "0", "1", "2", "4", "5"))); + ft_set_cur_cell(table, 0, 2); + assert_true(FT_IS_SUCCESS(ft_ln(table))); + ft_set_cur_cell(table, 1, 1); + assert_true(FT_IS_SUCCESS(ft_write_ln(table, "3"))); + + const char *table_str = ft_to_string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+---+---+\n" + "| 0 | 1 |\n" + "| 2 | 3 |\n" + "| 4 | 5 |\n" + "+---+---+\n"; + assert_str_equal(table_str, table_str_etalon); + + ft_destroy_table(table); + } + + SCENARIO("Test printf functions") { + ft_table_t *table = ft_create_table(); + assert_true(table != NULL); + ft_set_tbl_prop(table, FT_TPROP_ADDING_STRATEGY, FT_STRATEGY_INSERT); + assert_true(FT_IS_SUCCESS(ft_write_ln(table, "0", "1", "2", "5", "6"))); + ft_set_cur_cell(table, 0, 2); + assert_true(FT_IS_SUCCESS(ft_ln(table))); + ft_set_cur_cell(table, 1, 1); + assert_true(FT_IS_SUCCESS(ft_printf_ln(table, "3|4"))); + + const char *table_str = ft_to_string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+---+---+---+\n" + "| 0 | 1 | |\n" + "| 2 | 3 | 4 |\n" + "| 5 | 6 | |\n" + "+---+---+---+\n"; + assert_str_equal(table_str, table_str_etalon); + + ft_destroy_table(table); + } +} void test_table_copy(void) { diff --git a/tests/bb_tests_cpp/test_table_basic.cpp b/tests/bb_tests_cpp/test_table_basic.cpp index 6f35f68..29e3b88 100644 --- a/tests/bb_tests_cpp/test_table_basic.cpp +++ b/tests/bb_tests_cpp/test_table_basic.cpp @@ -300,6 +300,52 @@ void test_cpp_table_write(void) } } +void test_cpp_table_insert(void) +{ + SCENARIO("Test insert into beginning") { + fort::char_table table; + table.set_adding_strategy(fort::add_strategy::insert); + table.set_border_style(FT_BOLD_STYLE); + table << "val1" << "val2" << fort::endr + << "val3" << "val4" << fort::endr; + + table.set_cur_cell(0, 0); + table << fort::header + << "hdr1" << "hdr2" << fort::endr; + + std::string table_str = table.to_string(); + std::string table_str_etalon = + "┏━━━━━━┳━━━━━━┓\n" + "┃ hdr1 ┃ hdr2 ┃\n" + "┣━━━━━━╋━━━━━━┫\n" + "┃ val1 ┃ val2 ┃\n" + "┃ val3 ┃ val4 ┃\n" + "┗━━━━━━┻━━━━━━┛\n"; + assert_string_equal(table_str, table_str_etalon); + } + + SCENARIO("Test insert into the middle") { + fort::char_table table; + table.set_adding_strategy(fort::add_strategy::insert); + table.set_border_style(FT_BOLD_STYLE); + table << fort::header << "hdr1" << "hdr2" << fort::endr + << "val1" << "val4" << fort::endr; + + table.set_cur_cell(1, 1); + table << "val2" << fort::endr << "val3"; + + std::string table_str = table.to_string(); + std::string table_str_etalon = + "┏━━━━━━┳━━━━━━┓\n" + "┃ hdr1 ┃ hdr2 ┃\n" + "┣━━━━━━╋━━━━━━┫\n" + "┃ val1 ┃ val2 ┃\n" + "┃ val3 ┃ val4 ┃\n" + "┗━━━━━━┻━━━━━━┛\n"; + assert_string_equal(table_str, table_str_etalon); + } +} + void test_cpp_table_changing_cell(void) { WHEN("All columns are equal and not empty") { diff --git a/tests/main_test.c b/tests/main_test.c index b4ae459..0a990e0 100644 --- a/tests/main_test.c +++ b/tests/main_test.c @@ -16,6 +16,7 @@ void test_table_changing_cell(void); void test_wcs_table_boundaries(void); #endif void test_table_write(void); +void test_table_insert_strategy(void); void test_table_border_style(void); void test_table_builtin_border_styles(void); void test_table_cell_properties(void); @@ -49,6 +50,7 @@ struct test_case bb_test_suite [] = { {"test_utf8_table", test_utf8_table}, #endif {"test_table_write", test_table_write}, + {"test_table_insert_strategy", test_table_insert_strategy}, {"test_table_changing_cell", test_table_changing_cell}, {"test_table_border_style", test_table_border_style}, {"test_table_builtin_border_styles", test_table_builtin_border_styles}, diff --git a/tests/main_test_cpp.cpp b/tests/main_test_cpp.cpp index 3b76aa6..7c92e83 100644 --- a/tests/main_test_cpp.cpp +++ b/tests/main_test_cpp.cpp @@ -5,6 +5,7 @@ /* Test cases */ void test_cpp_table_basic(void); void test_cpp_table_write(void); +void test_cpp_table_insert(void); void test_cpp_table_changing_cell(void); void test_cpp_table_tbl_properties(void); void test_cpp_table_cell_properties(void); @@ -15,6 +16,7 @@ void test_cpp_bug_fixes(void); struct test_case bb_test_suite [] = { {"test_cpp_table_basic", test_cpp_table_basic}, {"test_cpp_table_write", test_cpp_table_write}, + {"test_cpp_table_insert", test_cpp_table_insert}, {"test_cpp_table_changing_cell", test_cpp_table_changing_cell}, {"test_cpp_table_tbl_properties", test_cpp_table_tbl_properties}, {"test_cpp_table_cell_properties", test_cpp_table_cell_properties}, diff --git a/tests/wb_tests/test_vector.c b/tests/wb_tests/test_vector.c index 5a1eafd..1e4a286 100644 --- a/tests/wb_tests/test_vector.c +++ b/tests/wb_tests/test_vector.c @@ -87,6 +87,30 @@ void test_vector_basic(void) } } + WHEN("Testing insert method vector") { + vector_clear(vector); + + size_t capacity = 10 * init_capacity; + for (i = 0; i < capacity; ++i) { + item_t item = (item_t)i; + vector_insert(vector, &item, 0); + } + assert_true(vector_size(vector) == capacity); + for (i = 0; i < capacity; ++i) { + assert_true(*(item_t *)vector_at(vector, i) == (item_t)(capacity - i) - 1); + } + + item_t item_666 = 666; + vector_insert(vector, &item_666, 5 * capacity - 1); + assert_true(vector_size(vector) == 5 * capacity); + assert_true(*(item_t *)vector_at(vector, 5 * capacity - 1) == item_666); + + item_t item_777 = 777; + vector_insert(vector, &item_777, 10); + assert_true(vector_size(vector) == 5 * capacity + 1); + assert_true(*(item_t *)vector_at(vector, 10) == item_777); + } + WHEN("Moving from another vector") { vector_clear(vector); for (i = 0; i < 10; ++i) {