Skip to content

Commit

Permalink
Extract print methods to seperate classes
Browse files Browse the repository at this point in the history
The printing methods are already pretty large, making it difficult to
add extra functionality. Extracting them to seperate classes allows
refactoring them for easier maintainability.

A lot of the functionality of calculating the terminal width can be
extracted to a separate object as well.
  • Loading branch information
p8 committed Aug 22, 2023
1 parent f98f4a9 commit 580234a
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 144 deletions.
152 changes: 11 additions & 141 deletions lib/thor/shell/basic.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
require_relative "column_printer"
require_relative "table_printer"
require_relative "wrapped_printer"

class Thor
module Shell
class Basic
DEFAULT_TERMINAL_WIDTH = 80

attr_accessor :base
attr_reader :padding

Expand Down Expand Up @@ -161,16 +163,8 @@ def no?(statement, color = nil)
# Array[String, String, ...]
#
def print_in_columns(array)
return if array.empty?
colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
array.each_with_index do |value, index|
# Don't output trailing spaces when printing the last column
if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
stdout.puts value
else
stdout.printf("%-#{colwidth}s", value)
end
end
printer = ColumnPrinter.new(stdout)
printer.print(array)
end

# Prints a table.
Expand All @@ -183,56 +177,8 @@ def print_in_columns(array)
# colwidth<Integer>:: Force the first column to colwidth spaces wide.
#
def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength
return if array.empty?

formats = []
indent = options[:indent].to_i
colwidth = options[:colwidth]
options[:truncate] = terminal_width if options[:truncate] == true

formats << "%-#{colwidth + 2}s".dup if colwidth
start = colwidth ? 1 : 0

colcount = array.max { |a, b| a.size <=> b.size }.size

maximas = []

start.upto(colcount - 1) do |index|
maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
maximas << maxima
formats << if index == colcount - 1
# Don't output 2 trailing spaces when printing the last column
"%-s".dup
else
"%-#{maxima + 2}s".dup
end
end

formats[0] = formats[0].insert(0, " " * indent)
formats << "%s"

array.each do |row|
sentence = "".dup

row.each_with_index do |column, index|
maxima = maximas[index]

f = if column.is_a?(Numeric)
if index == row.size - 1
# Don't output 2 trailing spaces when printing the last column
"%#{maxima}s"
else
"%#{maxima}s "
end
else
formats[index]
end
sentence << f % column.to_s
end

sentence = truncate(sentence, options[:truncate]) if options[:truncate]
stdout.puts sentence
end
printer = TablePrinter.new(stdout, options)
printer.print(array)
end

# Prints a long string, word-wrapping the text to the current width of the
Expand All @@ -245,33 +191,8 @@ def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength
# indent<Integer>:: Indent each line of the printed paragraph by indent value.
#
def print_wrapped(message, options = {})
indent = options[:indent] || 0
width = terminal_width - indent
paras = message.split("\n\n")

paras.map! do |unwrapped|
words = unwrapped.split(" ")
counter = words.first.length
words.inject do |memo, word|
word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
counter = 0 if word.include? "\n"
if (counter + word.length + 1) < width
memo = "#{memo} #{word}"
counter += (word.length + 1)
else
memo = "#{memo}\n#{word}"
counter = word.length
end
memo
end
end.compact!

paras.each do |para|
para.split("\n").each do |line|
stdout.puts line.insert(0, " " * indent)
end
stdout.puts unless para == paras.last
end
printer = WrappedPrinter.new(stdout, options)
printer.print(message)
end

# Deals with file collision and returns true if the file should be
Expand Down Expand Up @@ -321,19 +242,6 @@ def file_collision(destination)
end
end

# This code was copied from Rake, available under MIT-LICENSE
# Copyright (c) 2003, 2004 Jim Weirich
def terminal_width
result = if ENV["THOR_COLUMNS"]
ENV["THOR_COLUMNS"].to_i
else
unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
end
result < 10 ? DEFAULT_TERMINAL_WIDTH : result
rescue
DEFAULT_TERMINAL_WIDTH
end

# Called if something goes wrong during the execution. This is used by Thor
# internally and should not be used inside your scripts. If something went
# wrong, you can always raise an exception. If you raise a Thor::Error, it
Expand Down Expand Up @@ -416,46 +324,8 @@ def quiet? #:nodoc:
mute? || (base && base.options[:quiet])
end

# Calculate the dynamic width of the terminal
def dynamic_width
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
end

def dynamic_width_stty
`stty size 2>/dev/null`.split[1].to_i
end

def dynamic_width_tput
`tput cols 2>/dev/null`.to_i
end

def unix?
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i
end

def truncate(string, width)
as_unicode do
chars = string.chars.to_a
if chars.length <= width
chars.join
else
chars[0, width - 3].join + "..."
end
end
end

if "".respond_to?(:encode)
def as_unicode
yield
end
else
def as_unicode
old = $KCODE
$KCODE = "U"
yield
ensure
$KCODE = old
end
Terminal.unix?
end

def ask_simply(statement, color, options)
Expand Down
29 changes: 29 additions & 0 deletions lib/thor/shell/column_printer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require_relative "terminal"

class Thor
module Shell
class ColumnPrinter
attr_reader :stdout, :options

def initialize(stdout, options = {})
@stdout = stdout
@options = options
@indent = options[:indent].to_i
end

def print(array)
return if array.empty?
colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
array.each_with_index do |value, index|
# Don't output trailing spaces when printing the last column
if ((((index + 1) % (Terminal.terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
stdout.puts value
else
stdout.printf("%-#{colwidth}s", value)
end
end
end
end
end
end

95 changes: 95 additions & 0 deletions lib/thor/shell/table_printer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
require_relative "column_printer"
require_relative "terminal"

class Thor
module Shell
class TablePrinter < ColumnPrinter
def initialize(stdout, options = {})
super
@formats = []
@maximas = []
@colwidth = options[:colwidth]
@truncate = options[:truncate] == true ? Terminal.terminal_width : options[:truncate]
end

def print(array)
return if array.empty?

prepare(array)

array.each do |row|
sentence = "".dup

row.each_with_index do |column, index|
maxima = @maximas[index]

f = if column.is_a?(Numeric)
if index == row.size - 1
# Don't output 2 trailing spaces when printing the last column
"%#{maxima}s"
else
"%#{maxima}s "
end
else
@formats[index]
end
sentence << f % column.to_s
end

sentence = truncate(sentence)
stdout.puts sentence
end
end

private

def prepare(array)
@formats << "%-#{@colwidth + 2}s".dup if @colwidth
start = @colwidth ? 1 : 0

colcount = array.max { |a, b| a.size <=> b.size }.size

start.upto(colcount - 1) do |index|
maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
@maximas << maxima
@formats << if index == colcount - 1
# Don't output 2 trailing spaces when printing the last column
"%-s".dup
else
"%-#{maxima + 2}s".dup
end
end

@formats[0] = @formats[0].insert(0, " " * @indent)
@formats << "%s"
end

def truncate(string)
return string unless @truncate
as_unicode do
chars = string.chars.to_a
if chars.length <= @truncate
chars.join
else
chars[0, @truncate - 3].join + "..."
end
end
end

if "".respond_to?(:encode)
def as_unicode
yield
end
else
def as_unicode
old = $KCODE # rubocop:disable Style/GlobalVars
$KCODE = "U" # rubocop:disable Style/GlobalVars
yield
ensure
$KCODE = old # rubocop:disable Style/GlobalVars
end
end
end
end
end

42 changes: 42 additions & 0 deletions lib/thor/shell/terminal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class Thor
module Shell
module Terminal
DEFAULT_TERMINAL_WIDTH = 80

class << self
# This code was copied from Rake, available under MIT-LICENSE
# Copyright (c) 2003, 2004 Jim Weirich
def terminal_width
result = if ENV["THOR_COLUMNS"]
ENV["THOR_COLUMNS"].to_i
else
unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
end
result < 10 ? DEFAULT_TERMINAL_WIDTH : result
rescue
DEFAULT_TERMINAL_WIDTH
end

def unix?
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i
end

private

# Calculate the dynamic width of the terminal
def dynamic_width
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
end

def dynamic_width_stty
`stty size 2>/dev/null`.split[1].to_i
end

def dynamic_width_tput
`tput cols 2>/dev/null`.to_i
end

end
end
end
end
Loading

0 comments on commit 580234a

Please sign in to comment.