Renders tree-like objects and column-aligned attributes for each tree node
Inspect tree-like objects with fewer keystrokes.
Motivations:
- easier column-aligned printout for comparing attributes across rows,
- attribute dereference chaining and flexible transforms,
- customizable column headings,
- and visually appealing.
Add this line to your application's Gemfile:
gem 'console-tree-renderer'
And then execute:
$ bundle
Or install it yourself as:
$ gem install console-tree-renderer
After checking out the repo, run bin/setup
to install dependencies.
Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt
that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
.
To release a new version, update the version number in version.rb
, and
then run bundle exec rake release
, which will create a git tag for the version,
push git commits and tags, and push the .gem
file to rubygems.org.
See spec file for example usage.
> puts tree_holder.tree
# OR
> tree_holder.treee # <-- avoids having to prepend `puts`
┌────────────────────────────────────────────────────┐
TreeLikeObject::TreeHolder Holder-c71a3481-c5ec-401f-9efc-2b1c144cdb09 │ │ ID │ STATUS │
TreeLikeObject │ │ ad4e104e-2f57-4e02-8adb-706c58f5d10a │ created │
├── TreeLikeObject │ * │ da076c2f-627f-4b40-9209-249f8f9a3820 │ created │
│ └── TreeLikeObject │ │ 14ec3168-cd31-4837-beb7-6386d01f2dab │ created │
└── TreeLikeObject │ │ c5dbb05b-20a7-4ccd-8163-029493f820cf │ created │
├── TreeLikeObject │ │ b9eae1f0-e5b4-4573-a708-4d0d4926b94b │ created │
└── TreeLikeObject │ │ 1392f8e0-9cb6-4769-ae42-3e4030223e5f │ created │
└────────────────────────────────────────────────────┘
Customize the heading label by changing config.heading_label_template
-- search for instructions below.
Default attributes are shown as columns.
Customize the default attributes by changing config.default_atts
-- search for instructions below.
Specify columns to show:
> tree_holder.treee(:id, %i[assigned_to type])
# OR
> tree_holder.treee :id, %i[assigned_to type]
┌──────────────────────────────────────────────────────────────────────────────────┐
TreeLikeObject::TreeHolder Holder-c71a3481-c5ec-401f-9efc-2b1c144cdb09 │ ID │ [:ASSIGNED_TO, :TYPE] │
└── TreeLikeObject │ ad4e104e-2f57-4e02-8adb-706c58f5d10a │ type-e2af231d-3896-4ab7-ad53-cb6650e6c374 │
├── TreeLikeObject │ da076c2f-627f-4b40-9209-249f8f9a3820 │ type-87ed158a-8aa5-4705-a581-e647e1b4011e │
│ └── TreeLikeObject │ 14ec3168-cd31-4837-beb7-6386d01f2dab │ type-fb67f574-5d4d-4e25-9fba-8ffc06a26bc7 │
└── TreeLikeObject │ c5dbb05b-20a7-4ccd-8163-029493f820cf │ type-9ce37381-7a00-4a5a-b3ae-822ea0bda6b0 │
├── TreeLikeObject │ b9eae1f0-e5b4-4573-a708-4d0d4926b94b │ type-88a498bf-0a23-484e-ae8c-7287d815c419 │
└── TreeLikeObject │ 1392f8e0-9cb6-4769-ae42-3e4030223e5f │ type-babdd139-5366-4ac5-b50f-bdc9c51d6c5f │
└──────────────────────────────────────────────────────────────────────────────────┘
The default output is ANSI, which is more visually appealing. For more compact output without vertical lines and borders:
> tree_holder.global_renderer.compact_mode
> tree_holder.treee :id, :status
TreeLikeObject::TreeHolder Holder-b72a451c-00d7-40a5-a2c9-4f1de9c66088 ID STATUS
└── TreeLikeObject 4cf67a26-5891-4b75-a7e9-b4fe672dddb2 created
├── TreeLikeObject 9821516e-9338-4a2c-bcdf-7746e0aa64e3 created
│ └── TreeLikeObject 652a31c2-33e5-40f6-b7f8-3d9088bdffb2 created
└── TreeLikeObject bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 created
├── TreeLikeObject ece05ddc-f9b0-47ce-8acc-45adbfb43703 created
└── TreeLikeObject 7f214699-e09d-4095-8b4d-3d7af684c6fc created
For compatibility with text editors and other tools, change output to use tree_holder.global_renderer.ansi_mode
:
Restore ANSI outlines by calling tree_holder.global_renderer.ansi_mode
.
Row attributes can be dereferenced by using an array representing a chain of methods to call.
For example, to show the type
of the assigned_to
object, use [:assigned_to, :type]
.
By default the column heading will be [:ASSIGNED_TO, :TYPE]
.
To manually set the column headings, use the col_labels
named parameter as follows:
> atts = [:id, :status, :assigned_to, %i[parent id], %i[assigned_to type]]
> col_labels = ["\#", 'Status', 'AssignedTo', 'P_ID', 'ASGN_TO']
> tree_holder.treee(*atts, col_labels: col_labels)
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
TreeLikeObject::TreeHolder Holder-b72a451c-00d7-40a5-a2c9-4f1de9c66088 │ # │ Status │ AssignedTo │ P_ID │ ASGN_TO │
└── TreeLikeObject │ 4cf67a26-5891-4b75-a7e9-b4fe672dddb2 │ created │ #<TreeLikeObject::SomeUser:0x00007fb215939da0> │ │ type-8a725f7e-f68a-495a-926b-814e2f0e5c13 │
├── TreeLikeObject │ 9821516e-9338-4a2c-bcdf-7746e0aa64e3 │ created │ #<TreeLikeObject::SomeUser:0x00007fb215939968> │ 4cf67a26-5891-4b75-a7e9-b4fe672dddb2 │ type-7abb60b4-e6cd-4651-9f64-5e7452f0f3c9 │
│ └── TreeLikeObject │ 652a31c2-33e5-40f6-b7f8-3d9088bdffb2 │ created │ #<TreeLikeObject::SomeUser:0x00007fb2159390f8> │ 9821516e-9338-4a2c-bcdf-7746e0aa64e3 │ type-b9f0a0a6-1a9a-48a1-8b68-3aaf53c79437 │
└── TreeLikeObject │ bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 │ created │ #<TreeLikeObject::SomeUser:0x00007fb215939530> │ 4cf67a26-5891-4b75-a7e9-b4fe672dddb2 │ type-17447873-c854-4c7c-b55e-6cae7beb9a7c │
├── TreeLikeObject │ ece05ddc-f9b0-47ce-8acc-45adbfb43703 │ created │ #<TreeLikeObject::SomeUser:0x00007fb215938cc0> │ bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 │ type-c1f5818f-0124-44a7-bc27-c6258765952b │
└── TreeLikeObject │ 7f214699-e09d-4095-8b4d-3d7af684c6fc │ created │ #<TreeLikeObject::SomeUser:0x00007fb215938888> │ bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 │ type-f8d83657-e91f-425d-8a14-ef0bed4f020f │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
A more flexible alternative to attribute dereferencing is to use a Ruby lambda to derive values for a column.
In the example below, new columns with column headings ASGN_TO.TYPE
and :ASGN_TO_ID
will be included
when the columns are specified as parameters to tree
.
Note that a string or symbol can be used for the new column.
Call tree_holder.global_renderer.config.value_funcs_hash
to show a list of available attribute transforms.
For each row in the tree, the specified lambda is called to populate the values under the column.
> tree_holder.global_renderer.config.value_funcs_hash['ASGN_TO.TYPE'] = ->(row) { row.assigned_to.type }
> tree_holder.global_renderer.config.value_funcs_hash[:ASGN_TO_ID] = ->(row) { row.assigned_to.id }
> tree_holder.global_renderer.ansi_mode
> tree_holder.treee(:id, :status, 'ASGN_TO.TYPE', :ASGN_TO_ID)
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
TreeLikeObject::TreeHolder Holder-b72a451c-00d7-40a5-a2c9-4f1de9c66088 │ ID │ STATUS │ ASGN_TO.TYPE │ ASGN_TO_ID │
└── TreeLikeObject │ 4cf67a26-5891-4b75-a7e9-b4fe672dddb2 │ created │ type-8a725f7e-f68a-495a-926b-814e2f0e5c13 │ user-4cf67a26-5891-4b75-a7e9-b4fe672dddb2 │
├── TreeLikeObject │ 9821516e-9338-4a2c-bcdf-7746e0aa64e3 │ created │ type-7abb60b4-e6cd-4651-9f64-5e7452f0f3c9 │ user-9821516e-9338-4a2c-bcdf-7746e0aa64e3 │
│ └── TreeLikeObject │ 652a31c2-33e5-40f6-b7f8-3d9088bdffb2 │ created │ type-b9f0a0a6-1a9a-48a1-8b68-3aaf53c79437 │ user-652a31c2-33e5-40f6-b7f8-3d9088bdffb2 │
└── TreeLikeObject │ bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 │ created │ type-17447873-c854-4c7c-b55e-6cae7beb9a7c │ user-bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 │
├── TreeLikeObject │ ece05ddc-f9b0-47ce-8acc-45adbfb43703 │ created │ type-c1f5818f-0124-44a7-bc27-c6258765952b │ user-ece05ddc-f9b0-47ce-8acc-45adbfb43703 │
└── TreeLikeObject │ 7f214699-e09d-4095-8b4d-3d7af684c6fc │ created │ type-f8d83657-e91f-425d-8a14-ef0bed4f020f │ user-7f214699-e09d-4095-8b4d-3d7af684c6fc │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
The -
character is used to denote an error occurred when calling the lambda.
Set tree_holder.global_renderer.config.func_error_char
to change that character.
To highlight a specific row with an asterisk *
, add the " "
attribute as follows:
> row_to_highlight = @tree_holder.row_objects.sample
> row_to_highlight.treee " ", :id, :status
┌────────────────────────────────────────────────────┐
TreeLikeObject::TreeHolder Holder-b72a451c-00d7-40a5-a2c9-4f1de9c66088 │ │ ID │ STATUS │
TreeLikeObject │ │ 4cf67a26-5891-4b75-a7e9-b4fe672dddb2 │ created │
├── TreeLikeObject │ │ 9821516e-9338-4a2c-bcdf-7746e0aa64e3 │ created │
│ └── TreeLikeObject │ * │ 652a31c2-33e5-40f6-b7f8-3d9088bdffb2 │ created │
└── TreeLikeObject │ │ bec7b0bf-34d8-4a95-9ae7-32cc50af1a24 │ created │
├── TreeLikeObject │ │ ece05ddc-f9b0-47ce-8acc-45adbfb43703 │ created │
└── TreeLikeObject │ │ 7f214699-e09d-4095-8b4d-3d7af684c6fc │ created │
└────────────────────────────────────────────────────┘
By default, the calling row object is highlighted.
To highlight a different row, set the named parameter highlight
with the row object id like so:
> tree_holder.treee highlight: row_to_highlight.id
Change the default highlight character (*
) by setting tree_holder.global_renderer.config.highlight_char
.
Calling tree_holder.global_renderer
returns the default renderer,
a singleton which is used when the renderer:
parameter is not provided.
The heading_label_template
is a lambda that generates the heading line.
The heading_label_template
is evaluated with self
being the tree_holder.heading_object
object.
> tree_holder.global_renderer.config.heading_label_template = ->(tree_holder){ "#{tree_holder.id}" }
> tree_holder.treee :id, :status
┌────────────────────────────────────────────────┐
Holder-752b74be-6160-4747-8d3b-5940743d1c3b │ ID │ STATUS │
└── TreeLikeObject │ 5adad29e-dc59-4518-a9e3-1c0abc71630e │ created │
├── TreeLikeObject │ 3ff16a04-9592-4cfb-b476-fcf69f4c30f4 │ created │
│ └── TreeLikeObject │ f70cef9d-fcb4-415b-bdd6-8034012800db │ created │
└── TreeLikeObject │ b83052a4-141d-4a2f-9355-558d2803be25 │ created │
├── TreeLikeObject │ cabfe8f7-c33f-409a-9648-6118902693cf │ created │
└── TreeLikeObject │ 97f5c7f5-16ff-4df6-96af-c375959d3b7b │ created │
└────────────────────────────────────────────────┘
If named parameter col_labels
is not specified when calling tree
,
a column heading transform is used to create column labels. To see predefined heading transforms,
run tree_holder.global_renderer.config.heading_transform_funcs_hash
.
Add your own heading transform like this:
> tree_holder.global_renderer.config.heading_transform_funcs_hash[:downcase_headings] = ->(key, _col_obj) { key.downcase };
> tree_holder.global_renderer.config.heading_transform = :downcase_headings
> tree_holder.treee
┌────────────────────────────────────────────────────┐
Holder-752b74be-6160-4747-8d3b-5940743d1c3b │ │ id │ status │
└── TreeLikeObject │ │ 5adad29e-dc59-4518-a9e3-1c0abc71630e │ created │
├── TreeLikeObject │ │ 3ff16a04-9592-4cfb-b476-fcf69f4c30f4 │ created │
│ └── TreeLikeObject │ │ f70cef9d-fcb4-415b-bdd6-8034012800db │ created │
└── TreeLikeObject │ │ b83052a4-141d-4a2f-9355-558d2803be25 │ created │
├── TreeLikeObject │ │ cabfe8f7-c33f-409a-9648-6118902693cf │ created │
└── TreeLikeObject │ │ 97f5c7f5-16ff-4df6-96af-c375959d3b7b │ created │
└────────────────────────────────────────────────────┘
A heading_fill_str
is used in the top line between the tree_holder label and the column headings.
Change it by setting tree_holder.global_renderer.config.heading_fill_str
like so:
> tree_holder.global_renderer.config.heading_label_template = ->(tree_holder){ "Tree" }
> tree_holder.global_renderer.config.heading_fill_str = ". "
> tree_holder.treee :id, :status
┌────────────────────────────────────────────────┐
Tree. . . . . . . . . . . │ id │ status │
└── TreeLikeObject │ 5adad29e-dc59-4518-a9e3-1c0abc71630e │ created │
├── TreeLikeObject │ 3ff16a04-9592-4cfb-b476-fcf69f4c30f4 │ created │
│ └── TreeLikeObject │ f70cef9d-fcb4-415b-bdd6-8034012800db │ created │
└── TreeLikeObject │ b83052a4-141d-4a2f-9355-558d2803be25 │ created │
├── TreeLikeObject │ cabfe8f7-c33f-409a-9648-6118902693cf │ created │
└── TreeLikeObject │ 97f5c7f5-16ff-4df6-96af-c375959d3b7b │ created │
└────────────────────────────────────────────────┘
To customize column separators, internal margins, and customize borders modify other config
keys.
- Change the column separator by setting
:col_sep
. - Adjust internal margins by setting
:cell_margin_char
to""
(empty string) or" "
(a longer string). - Exclude top and bottom borders by setting
:include_border
to false. - The
:top_chars
and:bottom_chars
settings are used for the top and bottom borders respectively.- The first and fourth characters are used for the corners.
- The second character is used to draw horizontal lines.
- The third character is used at the column separator locations.
- (See
ConsoleTreeRenderer::ConsoleRenderer.write_border
for details).
- Change the highlight character by setting
:highlight_char
.
TreeHolder.global_renderer.config.tap do |conf|
conf.col_sep = " "
conf.cell_margin_char = ""
conf.include_border = true
conf.top_chars = "+-++"
conf.bottom_chars = "+-++"
conf.highlight_char = "#"
end
See the RendererConfig.ansi
and RendererConfig.ascii
functions for reference.
You can customize settings by modifying TreeHolder.global_renderer.config
in your .pryrc
so that it is loaded each time the Rails console is run.
For example, if your .pryrc
file in the Caseflow top-level directory contains the following:
TreeHolder.global_renderer.config.tap do |conf|
conf.default_atts = [:id, :status, :updated_at, :assigned_to_type, :ASGN_TO]
conf.heading_fill_str = ". "
end
puts "Tree renderer customized"
Then in a Rails console, running treee
will use your customizations.
You can define your own functions in .pryrc
that use customized renderers.
The tree1
method creates a renderer instance, customizes it, and
pass it as part of kwargs
to the treee
function.
The tree2
method creates a renderer instance, customizes it, and
uses it directly to call the tree_str
function.
def tree1(obj, *atts, **kwargs)
kwargs[:renderer] ||= TreeLikeObject::RenderModule.new_renderer
kwargs[:renderer].tap do |r|
r.compact_mode
r.config.default_atts = %i[id status]
end
obj.tree(*atts, **kwargs)
end
def tree2(obj, *atts, **kwargs)
kwargs.delete(:renderer) && raise("Use `tree1` method to allow 'renderer' named parameter!")
renderer = TreeLikeObject::RenderModule.new_renderer.tap do |r|
r.compact_mode
r.config.default_atts = %i[id status]
end
renderer.tree_str(obj, *atts, **kwargs)
end
Then use it like so:
> tree1 tree_holder
> tree2 tree_holder
Within byebug
, run load ".pryrc"
to load the customizations into the current byebug
environment.