#!/usr/bin/env ruby # -*- encoding: utf-8 -*- # Copyright Freya Dorn , 2017 # License: GNU APGLv3 (or later) module Enumerable # find shortest element def shortest self.min_by{|s| s.str_length} end # find longest element def longest self.max_by{|s| s.str_length} end def length_of_longest self.longest.str_length end def length_of_shortest self.shortest.str_length end end class Object # consistent string length def str_length s = self.to_s ss = StringScanner.new(s) len = s.length len -= ss.matched_size while ss.skip_until(String::ANSIColorRegexp) len end end class Array def align str="\t", alignment: :left, force: false, ansi: false, is_split: false just_function = case alignment when :left ; Proc.new{|e,l,_c| e.ljust l } when :right ; Proc.new{|e,l,_c| e.rjust l } when :center ; Proc.new{|e,l,_c| e.center l } when Proc ; alignment else raise "invalid alignment: #{alignment}" end # split all lines lines = [] columns = 0 rx = force ? /[ ]*#{str}/ : str self.each do |line| line = line.split(rx, -1) unless is_split lines << line columns = [columns, line.size].max end columns -= 1 # very last column is always un-aligned columns.times.map do |column| # calculate column width wants = [] new_block = false width = 0 first = 0 max_cols = column + 1 lines.each.with_index do |line, cur| if new_block new_block = false first = cur width = 0 end # treat last column and missing columns as useless w = line.size == max_cols ? nil : line[column]&.size if w.nil? # block done wants << [first, cur, width] unless width == 0 new_block = true else width = [width, w].max end end wants << [first, lines.size, width] unless new_block or width == 0 # last block # justify column wants.each do |from, to, width| lines[from...to].each do |line| if elem = line[column] # take into account how much the element is internally longer than it appears elem_diff = ansi ? elem.to_s.length - elem.str_length : 0 line[column] = just_function.call(elem, width + elem_diff, column) end end end end lines.map{|l| l.join(str)} end end