TTY
Toolbox for developing CLI clients in Ruby. This library provides a fluid interface for working with terminals.
Features
Jump-start development of your command line app:
- Table rendering with an easy-to-use API [status: In Progress]
- Terminal output colorization. [status: ✔ ]
- Terminal & System detection utilities. [status: In Progress]
- Text manipulation(wrapping/truncation) [status: In Progress]
- Shell user interface. [status: In Progress]
- File diffs. [status: TODO]
- Progress bar. [status: TODO]
- Configuration file management. [status: TODO]
- Logging [status: In Progress]
- Plugin ecosystem [status: TODO]
- Fully tested with major ruby interpreters.
- No dependencies to allow for easy gem vendoring.
Installation
Add this line to your application's Gemfile:
gem 'tty'
And then execute:
$ bundle
Or install it yourself as:
$ gem install tty
Usage
Table
To instantiate table pass 2-dimensional array:
table = TTY::Table[['a1', 'a2'], ['b1', 'b2']]
table = TTY::Table.new [['a1', 'a2'], ['b1', 'b2']]
table = TTY::Table.new rows: [['a1', 'a2'], ['b1', 'b2']]
table = TTY::Table.new ['h1', 'h2'], [['a1', 'a2'], ['b1', 'b2']]
table = TTY::Table.new header: ['h1', 'h2'], rows: [['a1', 'a2'], ['b1', 'b2']]
or cross header with rows inside a hash like so
table = TTY::Table.new [{'h1' => ['a1', 'a2'], 'h2' => ['b1', 'b2']}]
Apart from rows
and header
, you can provide other customization options such as
column_widths # array of maximum columns widths
column_aligns # array of cell alignments out of :left, :center and :right
renderer # enforce display type out of :basic, :color, :unicode, :ascii
orientation # either :horizontal or :vertical
border # hash of border properties out of :characters, :style, :separator keys
width # constrain the table total width, otherwise dynamically calculated based on content and terminal size
Table behaves like an Array so <<
, each
and familiar methods can be used
table << ['a1', 'a2', 'a3']
table << ['b1', 'b2', 'b3']
table << ['a1', 'a2'] << ['b1', 'b2'] # chain rows assignment
table.each { |row| ... } # iterate over rows
table.each_with_index # iterate over each element with row and column index
table[i, j] # return element at row(i) and column(j)
table.row(i) { ... } # return array for row(i)
table.column(j) { ... } # return array for column(j)
table.column(name) # return array for column(name), name of header
table.row_size # return row size
table.column_size # return column size
table.size # return an array of [row_size, column_size]
table.border # specify border properties
or pass your rows in a block
table = TTY::Table.new do |t|
t << ['a1', 'a2', 'a3']
t << ['b1', 'b2', 'b3']
end
And then to print do
table.to_s
a1 a2 a3
b1 b2 b3
Border
To print border around data table you need to specify renderer
type out of basic
, ascii
, unicode
. By default basic
is used. For instance, to output unicode border:
table = TTY::Table.new ['header1', 'header2'], [['a1', 'a2'], ['b1', 'b2'], renderer: 'unicode'
table.to_s
┌───────┬───────┐
│header1│header2│
├───────┼───────┤
│a1 │a2 │
│b1 │b2 │
└───────┴───────┘
You can also create your own custom border by subclassing TTY::Table::Border
and implementing the def_border
method using internal DSL methods like so:
class MyBorder < TTY::Table::Border
def_border do
left '$'
center '$'
right '$'
bottom ' '
bottom_mid '*'
bottom_left '*'
bottom_right '*'
end
end
Next pass the border class to your instantiated table
table = TTY::Table.new ['header1', 'header2'], [['a1', 'a2'], ['b1', 'b2']
table.renders_with MyBorder
table.to_s
$header1$header2$
$a1 $a2 $
* * *
Finally, if you want to introduce slight modifications to the predefined border types, you can use table border
helper like so
table = TTY::Table.new ['header1', 'header2'], [['a1', 'a2'], ['b1', 'b2']
table.border do
mid '='
mid_mid ' '
end
table.to_s
header1 header2
======= =======
a1 a2
b1 b2
In addition to specifying border characters you can force table to render separator line on each row like:
table = TTY::Table.new ['header1', 'header2'], [['a1', 'a2'], ['b1', 'b2']]
table.border.separator = :each_row
table.to_s
+-------+-------+
|header1|header2|
+-------+-------+
|a1 |a2 |
+-------+-------+
|b1 |b2 |
+-------+-------+
Also to change the display color of your border do:
table.border.style = :red
Alignment
All columns are left aligned by default. You can enforce per column alignment by passing column_aligns
option like so
rows = [['a1', 'a2'], ['b1', 'b2']
table = TTY::Table.new rows: rows, column_aligns: [:center, :right]
To align a single column do
table.align_column(1, :right)
If you require a more granular alignment you can align individual fields in a row by passing align
option
table = TTY::Table.new do |t|
t << ['a1', 'a2', 'a3']
t << ['b1', {:value => 'b2', :align => :right}, 'b3']
t << ['c1', 'c2', {:value => 'c3', :align => :center}]
end
Style
To format individual fields/cells do
table = TTY::Table.new rows: rows, width: 40
Filter
You can define filters that will modify individual table fields value before they are rendered. A filter can be a callable such as proc. Here's an example that formats
table = TTY::Table.new ['header1', 'header2'], [['a1', 'a2'], ['b1', 'b2']
table.filter = Proc.new do |val, row_index, col_index|
if col_index == 1 and !(row_index == 0)
val.capitalize
end
end
table.to_s
+-------+-------+
|header1|header2|
+-------+-------+
|a1 |A2 |
+-------+-------+
|b1 |B2 |
+-------+-------+
To add background color to even fields do
</code>
Terminal
To read general terminal properties you can use on of the helpers
term = TTY::Terminal.new
term.width # => 140
term.height # => 60
term.color? # => true or false
term.echo(false) { } # switch off echo for the block
To colorize your output do
term.color.set 'text...', :bold, :red, :on_green # => red bold text on green background
term.color.remove 'text...' # strips off ansi escape sequences
term.color.code :red # ansi escape code for the supplied color
Shell
Main responsibility is to interact with the prompt and provide convenience methods.
Available methods are
shell = TTY::Shell.new
shell.ask # print question
shell.read # read from stdin
shell.say # print message to stdout
shell.confirm # print message(s) in green
shell.warn # print message(s) in yellow
shell.error # print message(s) in red
shell.print_table # print table to stdout
In order to ask question and parse answers:
shell = TTY::Shell.new
answer = shell.ask("What is your name?").read_string
The library provides small DSL to help with parsing and asking precise questions
argument # :required or :optional
character # turn character based input, otherwise line (default: false)
clean # reset question
default # default value used if none is provided
echo # turn echo on and off (default: true)
mask # mask characters i.e '****' (default: false)
modify # apply answer modification :upcase, :downcase, :trim, :chomp etc..
range # specify range '0-9', '0..9', '0...9' or negative '-1..-9'
validate # regex against which stdin input is checked
valid # a list of expected valid options
You can chain question methods or configure them inside a block
shell.ask("What is your name?").argument(:required).default('Piotr').validate(/\w+\s\w+/).read_string
shell.ask "What is your name?" do
argument :required
default 'Piotr'
validate /\w+\s\w+/
valid ['Piotr', 'Piotrek']
modify :capitalize
end.read_string
Reading answers and converting them into required types can be done with custom readers
read_bool # return true or false for strings such as "Yes", "No"
read_date # return date type
read_datetime # return datetime type
read_email # validate answer against email regex
read_float # return decimal or error if cannot convert
read_int # return integer or error if cannot convert
read_multiple # return multiple line string
read_password # return string with echo turned off
read_range # return range type
read_string # return string
For example, if we wanted to ask a user for a single digit in given range
ask("Provide number in range: 0-9") do
range '0-9'
on_error :retry
end.read_int
on the other hand, if we are interested in range answer then
ask("Provide range of numbers?").read_range
System
TTY::System.unix? # => true
TTY::System.windows? # => false
TTY::System.which(cmd) # full path to executable if found, nil otherwise
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
Copyright
Copyright (c) 2012-2013 Piotr Murach. See LICENSE for further details.