0.0
Low commit activity in last 3 years
No release in over a year
IndentedIO extends Kernel, IO, and StringIO with an #indent method that redefines #print, printf, #puts, and #p to print their output indented. Indentations are stacked so that each new indentation adds to the previous indendation
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

IndentedIO

IndentedIO extends Kernel, IO, and StringIO with an #indent method that returns an IndentedIO object. The IndentedIO object acts as the original object but redefines #print, #printf, #puts, and #p to print their output indented. Indentations are stacked so that each new indentation adds to the previous indendation

Usage

require 'indented_io'

puts 'Not indented'
indent { puts 'Indented one level' }
indent(2, '* ').puts 'Indented two levels'

outputs

Not indented
  Indented one level
* * Indented two levels

Kernel#indent, IO#indent, and StringIO#indent

#indent without a block returns an IndentedIO object that acts as the receiver but redefine #print, #printf, #puts, and #p to print indented If given a block, the block will be called with the IndentedIO object as argument:

$stdout.puts 'Not indented'
$stdout.indent.puts 'Indented'
$stdout.indent { |f| f.puts 'Indented' }

# Not indented
#   Indented
#   Indented

(please note that when Kernel is the receiver, the returned object will act as the $stdout object and not the Kernel object)

#indent can take up to two positional arguments: level, that is the number of levels to indent (default 1), and the indent string that defaults to the indent string of the previous level or IndentedIO.default_string if at the first level. It is also possible to specify the indentation string using the symbolic argument :string. If level is negative, the text will be outdented instead:

$stdout.puts 'Not indented'
$stdout.indent(2, '> ') do |f|
  f.indent(string: '* ').puts 'Indented three levels'
  f.indent(-1).puts 'Indented one level'
end

# Not indented
# > > * Indented three levels
# > Indented one level

When text is outdented, the indentation string defaults to the previous level's indentation string - not the parent's

Kernel#indent {}

If given a block without an argument, Kernel#indent manipulates $stdout so that Kernel#print, Kernel#printf, Kernel#puts, and Kernel#p will output indented within that block:

puts 'Not indented'
indent do
  puts 'Indented one level'
  indent do
    puts 'Indented two levels'
  end
  puts 'Indented one level'
end
puts 'Not indented'

# Not indented
#   Indented one level
#     Indented two levels
#   Indented one level
# Not indented

Because this manipulates $stdout, the indentation carries through to methods that doesn't even know about IndentedIO:

def legacy(phrase)
  puts phrase
end

legacy('Not indented')
indent { legacy('Indented' }

# Not indented
#   Indented

This is probably the style that'll be used most of the time. It is of course still possible use Kernel#indent with a block argument if needed

bol - Beginning-Of-Line argument

#indent takes a symbolic :bol argument (true or false, default true) that specify if the output device is at the beginning of a line and that printing should start with an indentation string:

indent(1, bol: true).puts 'Indented'
indent(1, bol: false).puts 'Not indented\nIndented'

#   Indented
# Not indented
#   Indented

Constants

The default indentation string is defined in IndentedIO:

IndentedIO.default_indent = '>> '
indent.puts "Indented by #{IndentedIO.default_indent.inspect}"

# >> Indented by ">> "

The default at start-up is two spaces. It should normally be set only once at the start of the program. Use the indent string argument to #indent to get a different indentation for a part of the program

Exceptions

In case of errors an IndentedIO::Error exception is raised

Adding support for other classes

You can add support for your own IO objects by including IndentedIO::IndentedIOInterface in your class. All that is required is that the class define a #write method with the same semantics as IO#write (convert arguments to strings and then write them)

require 'indented_io'
class MyIO
  include IndentedIO::IndentedIOInterface
  def write(*args) ... end
end

my_io = MyIO.new
my_io.puts 'Not indented'
my_io.indent.puts 'It works!'

# Not indented
#   It works!

Implementation & performance

IndentedIO is intrusive because it extends the standard classes Kernel, IO, and StringIO with the #indent method. In addition, Kernel#indent with a block without parameters manipulates $stdout, replacing it with an IndentedIO object for the duration of the block

The implementation carries no overhead if it is not used but the core indentation mechanism processes characters one-by-one which is about 7-8 times slower than a handwritten implementation (scripts/perf.rb is a script to check performance). It would be much faster if the inner loop was implemented in C. However, we're talking micro-seconds here: Printing without using IndentedIO range from around 0.25us to 1us while using IndentedIO slows it down to between 2us and 8us, so IndentedIO won't cause a noticeable slow down of your application unless you do a lot of output

Installation

Add this line to your application's Gemfile:

gem 'indented_io'

And then execute:

$ bundle

Or install it yourself as:

$ gem install indented_io

Documentation

API documentation is on Rubydoc

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/clrgit/indented_io.

License

The gem is available as open source under the terms of the MIT License.