diff --git a/docs/src/man/text_backend.md b/docs/src/man/text_backend.md index 4fde2400..84008441 100644 --- a/docs/src/man/text_backend.md +++ b/docs/src/man/text_backend.md @@ -71,6 +71,18 @@ passed as keywords when calling the function `pretty_table`: row number. (**Default** = `false`) * `tf`: Table format used to print the table (see the section [Text table formats](@ref)). (**Default** = `unicode`) +* `vlines`: This variable controls where the vertical lines will be drawn. It + can be `:all` or a vector of integers. In the first case (the + default behavior), all vertical lines will be drawn. In the second + case, the vertical lines will be drawn only after the columns in the + vector. Notice that the left border will be drawn if `0` is in + `vlines`. Furthermore, it is important to mention that the column + number in this variable is related to the **printed columns**. Thus, + it is affected by filters, and by the columns added using the + variables `show_row_number` and `row_names`. Finally, for + convenience, the right border can be drawn by adding the symbol + `:end` to this vector, which will be replaced by the number of the + last printed column. (**Default** = `:all`) The keywords `header_crayon` and `subheaders_crayon` can be a `Crayon` or a `Vector{Crayon}`. In the first case, the `Crayon` will be applied to all the diff --git a/src/backends/text/print.jl b/src/backends/text/print.jl index 41d10807..6b5e2885 100644 --- a/src/backends/text/print.jl +++ b/src/backends/text/print.jl @@ -30,7 +30,8 @@ function _pt_text(io, pinfo; screen_size::Union{Nothing,Tuple{Int,Int}} = nothing, show_row_number::Bool = false, sortkeys::Bool = false, - tf::TextFormat = unicode) + tf::TextFormat = unicode, + vlines::Union{Symbol,AbstractVector} = :all) @unpack_PrintInfo pinfo @@ -264,12 +265,38 @@ function _pt_text(io, pinfo; show_row_names && (all_cols_width = vcat(row_name_width, all_cols_width)) show_row_number && (all_cols_width = vcat(row_number_width, all_cols_width)) + # Process `vlines`. + if vlines == :all + vlines = collect(0:1:length(all_cols_width)) + elseif !(typeof(vlines) <: AbstractVector) + error("`vlines` must be `:all` or an vector of integers.") + end + + # The symbol `:end` is replaced by the last column. + vlines = replace(vlines, :end => length(all_cols_width)) + + # Auxiliary variables to verify if the vertical line must be drawn in the + # row number and row name. + row_number_vline = 1 ∈ vlines + row_name_vline = false + + if show_row_names + if show_row_number + row_name_vline = 2 ∈ vlines + else + row_name_vline = 1 ∈ vlines + end + end + + # `Δc` store the number of rows that the user added before that data. + Δc = Int(show_row_number + show_row_names) + # Top table line # ========================================================================== tf.top_line && _draw_line!(screen, buf, tf.up_left_corner, tf.up_intersection, tf.up_right_corner, tf.row, - border_crayon, all_cols_width) + border_crayon, all_cols_width, vlines) # Header # ========================================================================== @@ -279,7 +306,7 @@ function _pt_text(io, pinfo; if !noheader @inbounds @views for i = 1:header_num_rows - _p!(screen, buf, border_crayon, tf.left_border) + 0 ∈ vlines && _p!(screen, buf, border_crayon, tf.left_border) if show_row_number # The text "Row" must appear only on the first line. @@ -290,7 +317,7 @@ function _pt_text(io, pinfo; _p!(screen, buf, rownum_header_crayon, " "^(row_number_width+2)) end - _p!(screen, buf, border_crayon, tf.column) + _pc!(row_number_vline, screen, buf, border_crayon, tf.column, " ") end # If we have row name column, then print in the first line the @@ -306,7 +333,7 @@ function _pt_text(io, pinfo; _p!(screen, buf, text_crayon, " "^(row_name_width+2)) end - _p!(screen, buf, border_crayon, tf.column) + _pc!(row_name_vline, screen, buf, border_crayon, tf.column, " ") end for j = 1:num_printed_cols @@ -322,11 +349,15 @@ function _pt_text(io, pinfo; flp = j == num_printed_cols _p!(screen, buf, crayon, header_i_str) + if j != num_printed_cols - _p!(screen, buf, border_crayon, tf.column, flp) + _pc!(j + Δc ∈ vlines, screen, buf, border_crayon, tf.column, + " " , flp) else - _p!(screen, buf, border_crayon, tf.right_border, flp) + _pc!(j + Δc ∈ vlines, screen, buf, border_crayon, + tf.right_border, " " , flp) end + _eol(screen) && break end @@ -341,7 +372,7 @@ function _pt_text(io, pinfo; tf.header_line && _draw_line!(screen, buf, tf.left_intersection, tf.middle_intersection, tf.right_intersection, tf.row, - border_crayon, all_cols_width) + border_crayon, all_cols_width, vlines) end # Data @@ -351,7 +382,7 @@ function _pt_text(io, pinfo; ir = id_rows[i] for l = 1:num_lines_in_row[i] - _p!(screen, buf, border_crayon, tf.left_border) + 0 ∈ vlines && _p!(screen, buf, border_crayon, tf.left_border) if show_row_number if l == 1 @@ -361,7 +392,7 @@ function _pt_text(io, pinfo; end _p!(screen, buf, text_crayon, row_number_i_str) - _p!(screen, buf, border_crayon, tf.column) + _pc!(row_number_vline, screen, buf, border_crayon, tf.column, " ") end if show_row_names @@ -369,7 +400,7 @@ function _pt_text(io, pinfo; row_name_alignment, row_name_width) * " " _p!(screen, buf, row_name_crayon, row_names_i_str) - _p!(screen, buf, border_crayon, tf.column) + _pc!(row_name_vline, screen, buf, border_crayon, tf.column, " ") end for j = 1:num_printed_cols @@ -407,10 +438,13 @@ function _pt_text(io, pinfo; flp = j == num_printed_cols if j != num_printed_cols - _p!(screen, buf, border_crayon, tf.column, flp) + _pc!(j + Δc ∈ vlines, screen, buf, border_crayon, tf.column, + " " , flp) else - _p!(screen, buf, border_crayon, tf.right_border, flp) + _pc!(j + Δc ∈ vlines, screen, buf, border_crayon, + tf.right_border, " " , flp) end + _eol(screen) && break end @@ -420,14 +454,15 @@ function _pt_text(io, pinfo; # Check if we must draw a horizontal line here. i != num_rows && i in hlines && - _draw_line!(screen, buf, hlines_format..., border_crayon, all_cols_width) + _draw_line!(screen, buf, hlines_format..., border_crayon, + all_cols_width, vlines) # Here we must check if the vertical size of the screen has been # reached. Notice that we must add 4 to account for the command line, # the continuation line, the bottom table line, and the last blank line. if (screen.size[1] > 0) && (screen.row + 4 >= screen.size[1]) _draw_continuation_row(screen, buf, tf, text_crayon, border_crayon, - all_cols_width) + all_cols_width, vlines) break end end @@ -438,7 +473,7 @@ function _pt_text(io, pinfo; tf.bottom_line && _draw_line!(screen, buf, tf.bottom_left_corner, tf.bottom_intersection, tf.bottom_right_corner, tf.row, border_crayon, - all_cols_width) + all_cols_width, vlines) # Print the buffer # ========================================================================== diff --git a/src/backends/text/private.jl b/src/backends/text/private.jl index 10e7c83f..1a9b91fb 100644 --- a/src/backends/text/private.jl +++ b/src/backends/text/private.jl @@ -128,18 +128,18 @@ end ################################################################################ """ - _draw_continuation_row(screen, io, tf, text_crayon, border_crayon, num_printed_cols, cols_width, show_row_number, row_number_width, show_row_names, row_name_width) + _draw_continuation_row(screen, io, tf, text_crayon, border_crayon, cols_width, vlines) Draw the continuation row when the table has filled the vertical space available. This function prints in each column the character `⋮` centered. """ function _draw_continuation_row(screen, io, tf, text_crayon, border_crayon, - cols_width) + cols_width, vlines) num_cols = length(cols_width) - _p!(screen, io, border_crayon, tf.column) + 0 ∈ vlines && _p!(screen, io, border_crayon, tf.column) @inbounds for j = 1:num_cols data_ij_str = _str_aligned("⋮", :c, cols_width[j] + 2) @@ -147,7 +147,7 @@ function _draw_continuation_row(screen, io, tf, text_crayon, border_crayon, flp = j == num_cols - _p!(screen, io, border_crayon, tf.column, flp) + _pc!(j ∈ vlines, screen, io, border_crayon, tf.column, " ", flp) _eol(screen) && break end @@ -163,20 +163,21 @@ Draw a vertical line in `io` using the information in `screen`. """ function _draw_line!(screen, io, left, intersection, right, row, border_crayon, - cols_width) + cols_width, vlines) num_cols = length(cols_width) - _p!(screen, io, border_crayon, left) + 0 ∈ vlines && _p!(screen, io, border_crayon, left) @inbounds for i = 1:num_cols # Check the alignment and print. _p!(screen, io, border_crayon, row^(cols_width[i]+2)) && break - i != num_cols && _p!(screen, io, border_crayon, intersection) + i != num_cols && + _pc!(i ∈ vlines, screen, io, border_crayon, intersection, row) end - _p!(screen, io, border_crayon, right, true) + _pc!(num_cols ∈ vlines, screen, io, border_crayon, right, row, true) _nl!(screen, io) end @@ -297,3 +298,23 @@ function _p!(screen, io, crayon, str, final_line_print = false) # Return if we reached the end of line. return _eol(screen) end + +""" + _pc!(cond, screen, io, crayon, str_true, str_false, final_line_print = false) + +If `cond == true` then print `str_true`. Otherwise, print `str_false`. Those +strings will be printed into `io` using the Crayon `crayon` with the screen +information in `screen`. The parameter `final_line_print` must be set to `true` +if this is the last string that will be printed in the line. This is necessary +for the algorithm to select whether or not to include the continuation +character. + +""" +function _pc!(cond, screen, io, crayon, str_true, str_false, + final_line_print = false) + if cond + return _p!(screen, io, crayon, str_true, final_line_print) + else + return _p!(screen, io, crayon, str_false, final_line_print) + end +end diff --git a/src/print.jl b/src/print.jl index 821aa410..18ba31b7 100644 --- a/src/print.jl +++ b/src/print.jl @@ -179,6 +179,18 @@ This back-end produces text tables. This back-end can be used by selecting row number. (**Default** = `false`) * `tf`: Table format used to print the table (see `TextFormat`). (**Default** = `unicode`) +* `vlines`: This variable controls where the vertical lines will be drawn. It + can be `:all` or a vector of integers. In the first case (the + default behavior), all vertical lines will be drawn. In the second + case, the vertical lines will be drawn only after the columns in the + vector. Notice that the left border will be drawn if `0` is in + `vlines`. Furthermore, it is important to mention that the column + number in this variable is related to the **printed columns**. Thus, + it is affected by filters, and by the columns added using the + variables `show_row_number` and `row_names`. Finally, for + convenience, the right border can be drawn by adding the symbol + `:end` to this vector, which will be replaced by the number of the + last printed column. (**Default** = `:all`) The keywords `header_crayon` and `subheaders_crayon` can be a `Crayon` or a `Vector{Crayon}`. In the first case, the `Crayon` will be applied to all the diff --git a/test/text_backend.jl b/test/text_backend.jl index 238bf04f..d9824b90 100644 --- a/test/text_backend.jl +++ b/test/text_backend.jl @@ -1150,6 +1150,113 @@ end @test result == expected end +# Vertical lines +# ============================================================================== + +@testset "Vertical lines" begin + expected = """ +──────────────────────────── + C1 C2 C3 C4 + Int Bool Float Hex +──────────────────────────── + 1 false 1.0 1 + 2 true 2.0 2 + 3 false 3.0 3 + 4 true 4.0 4 + 5 false 5.0 5 + 6 true 6.0 6 +──────────────────────────── +""" + + result = pretty_table(String, data, + ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"], + vlines = []) + @test result == expected + + expected = """ +┌───────────────────────────┐ +│ C1 C2 C3 C4 │ +│ Int Bool Float Hex │ +├───────────────────────────┤ +│ 1 false 1.0 1 │ +│ 2 true 2.0 2 │ +│ 3 false 3.0 3 │ +│ 4 true 4.0 4 │ +│ 5 false 5.0 5 │ +│ 6 true 6.0 6 │ +└───────────────────────────┘ +""" + + result = pretty_table(String, data, + ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"], + vlines = [0,:end]) + @test result == expected + + expected = """ +┌─────┬───────────────────────────┐ +│ Row │ C1 C2 C3 C4 │ +│ │ Int Bool Float Hex │ +├─────┼───────────────────────────┤ +│ 1 │ 1 false 1.0 1 │ +│ 2 │ 2 true 2.0 2 │ +│ 3 │ 3 false 3.0 3 │ +│ 4 │ 4 true 4.0 4 │ +│ 5 │ 5 false 5.0 5 │ +│ 6 │ 6 true 6.0 6 │ +└─────┴───────────────────────────┘ +""" + + result = pretty_table(String, data, + ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"], + vlines = [0,1,:end], + show_row_number = true) + @test result == expected + + expected = """ +┌─────┬───────────┬───────────────────────────┐ +│ Row │ Row names │ C1 C2 C3 C4 │ +│ │ │ Int Bool Float Hex │ +├─────┼───────────┼───────────────────────────┤ +│ 1 │ Row 1 │ 1 false 1.0 1 │ +│ 2 │ Row 2 │ 2 true 2.0 2 │ +│ 3 │ Row 3 │ 3 false 3.0 3 │ +│ 4 │ Row 4 │ 4 true 4.0 4 │ +│ 5 │ Row 5 │ 5 false 5.0 5 │ +│ 6 │ Row 6 │ 6 true 6.0 6 │ +└─────┴───────────┴───────────────────────────┘ +""" + + result = pretty_table(String, data, + ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"], + vlines = [0,1,2,:end], + show_row_number = true, + row_names = ["Row $i" for i = 1:6], + row_name_column_title = "Row names", + row_name_alignment = :c) + @test result == expected + + expected = """ +┌─────┬───────────┬─────────────┬ ⋯ +│ Row │ Row names │ C1 C2 │ ⋯ +│ │ │ Int Bool │ ⋯ +├─────┼───────────┼─────────────┼ ⋯ +│ 1 │ Row 1 │ 1 false │ ⋯ +│ 2 │ Row 2 │ 2 true │ ⋯ +│ ⋮ │ ⋮ │ ⋮ ⋮ │ ⋯ +└─────┴───────────┴─────────────┴ ⋯ +""" + + result = pretty_table(String, data, + ["C1" "C2" "C3" "C4"; "Int" "Bool" "Float" "Hex"], + vlines = [0,1,2,4,:end], + show_row_number = true, + row_names = ["Row $i" for i = 1:6], + row_name_column_title = "Row names", + row_name_alignment = :c, + screen_size = (11,35)) + @test result == expected +end + # Test if we can print `missing` and `nothing` # ==============================================================================