├── .gitignore ├── .rspec ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── benchmarks └── speed_profile.rb ├── lib ├── verse.rb └── verse │ ├── alignment.rb │ ├── padder.rb │ ├── padding.rb │ ├── sanitizer.rb │ ├── truncation.rb │ ├── version.rb │ └── wrapping.rb ├── spec ├── spec_helper.rb └── unit │ ├── align_spec.rb │ ├── alignment │ ├── align_spec.rb │ ├── left_spec.rb │ └── right_spec.rb │ ├── pad_spec.rb │ ├── padder │ ├── accessors_spec.rb │ └── parse_spec.rb │ ├── padding │ └── pad_spec.rb │ ├── sanitizer │ ├── ansi_spec.rb │ ├── replace_spec.rb │ └── sanitize_spec.rb │ ├── truncate_spec.rb │ ├── truncation │ ├── new_spec.rb │ └── truncate_spec.rb │ ├── wrap_spec.rb │ └── wrapping │ └── wrap_spec.rb ├── tasks ├── console.rake ├── coverage.rake └── spec.rake └── verse.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --warning 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | sudo: false 4 | cache: bundler 5 | language: ruby 6 | bundler_args: --without benchmarks 7 | script: "bundle exec rake ci" 8 | rvm: 9 | - 2.0.0 10 | - 2.1.0 11 | - 2.2.5 12 | - 2.3.1 13 | - jruby-9.1.1.0 14 | - jruby-head 15 | - ruby-head 16 | matrix: 17 | allow_failures: 18 | - rvm: jruby-head 19 | - rvm: ruby-head 20 | fast_finish: true 21 | branches: 22 | only: master 23 | notifications: 24 | email: false 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## [v0.5.0] - 2016-10-25 4 | 5 | ### Added 6 | * Add ParseError to Padder 7 | 8 | ### Changed 9 | * Change Sanitizer to module 10 | * Replaces UnicodeUtils.display_width with Unicode::DisplayWidth.of by @MichaelBaker 11 | 12 | ## [v0.4.0] - 2015-03-28 13 | 14 | ### Added 15 | * Add Sanitizer#ansi? to check for ANSI codes 16 | 17 | ### Changed 18 | * Change Alignment to work with ANSI codes 19 | * Change Truncation to work with ANSI codes 20 | * Change Wrapping to work with ANSI codes 21 | * Chnage Padding to work with ANSI codes 22 | 23 | ## [v0.3.0] - 2015-02-28 24 | 25 | ### Added 26 | * Add Sanitizer#replace for substitutiong linebreaks 27 | * Add Padder for parsing padding values 28 | * Add Padding for padding content around 29 | 30 | ### Changed 31 | * Change Wrapping#wrap to preserve whitespace characters 32 | 33 | ## [v0.2.1] - 2015-02-15 34 | 35 | ### Fixed 36 | * Fix empty string alignment 37 | * Fix alignment to stop modifying original content 38 | 39 | ## [v0.2.0] - 2015-02-15 40 | 41 | ### Added 42 | * Add unicode support 43 | 44 | ### Changed 45 | * Change wrap, truncate and align to work with unicode characters 46 | 47 | ### Removed 48 | * Remove padding and indent from Verse::Wrapping 49 | 50 | ## [v0.1.0] - 2015-02-07 51 | 52 | * Inital implementation and release 53 | 54 | [v0.5.0]: https://github.com/piotrmurach/verse/compare/v0.4.0...v0.5.0 55 | [v0.4.0]: https://github.com/piotrmurach/verse/compare/v0.3.0...v0.4.0 56 | [v0.3.0]: https://github.com/piotrmurach/verse/compare/v0.2.1...v0.3.0 57 | [v0.2.1]: https://github.com/piotrmurach/verse/compare/v0.2.0...v0.2.1 58 | [v0.2.0]: https://github.com/piotrmurach/verse/compare/v0.1.0...v0.2.0 59 | [v0.1.0]: https://github.com/piotrmurach/verse/compare/v0.1.0 60 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'rake', '~> 11.1.1' 7 | gem 'rspec', '~> 3.5.0' 8 | gem 'yard', '~> 0.8.7' 9 | end 10 | 11 | group :metrics do 12 | gem 'coveralls', '~> 0.8.2' 13 | gem 'simplecov', '~> 0.10.0' 14 | gem 'yardstick', '~> 0.9.9' 15 | end 16 | 17 | group :benchmarks do 18 | gem 'benchmark-ips' 19 | end 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Piotr Murach 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Verse (Inactive, code moved to strings gem and repo.) 2 | 3 | [![Gem Version](https://badge.fury.io/rb/verse.svg)][gem] 4 | [![Build Status](https://secure.travis-ci.org/piotrmurach/verse.svg?branch=master)][travis] 5 | [![Code Climate](https://codeclimate.com/github/piotrmurach/verse/badges/gpa.png)][codeclimate] 6 | [![Coverage Status](https://coveralls.io/repos/piotrmurach/verse/badge.svg)][coverage] 7 | [![Inline docs](http://inch-ci.org/github/piotrmurach/verse.svg)][inchpages] 8 | 9 | [gem]: http://badge.fury.io/rb/verse 10 | [travis]: http://travis-ci.org/piotrmurach/verse 11 | [codeclimate]: https://codeclimate.com/github/piotrmurach/verse 12 | [coverage]: https://coveralls.io/r/piotrmurach/verse 13 | [inchpages]: http://inch-ci.org/github/piotrmurach/verse 14 | 15 | > Text transformations such as truncation, wrapping, aligning, indentation and grouping of words. 16 | 17 | **The functionality of this gem has been reimplemented and added to the strings gem @ [https://github.com/piotrmurach/strings](https://github.com/piotrmurach/strings). The code in this repository is no longer maintained.** 18 | 19 | ## Features 20 | 21 | * No monkey-patching String class 22 | * Simple API that can be easily wrapped by other objects 23 | * Supports multibyte character encodings such as UTF-8, EUC-JP 24 | * Handles languages without whitespaces between words (like Chinese and Japanese) 25 | * Supports ANSI escape codes 26 | 27 | ## Installation 28 | 29 | Add this line to your application's Gemfile: 30 | 31 | ```ruby 32 | gem 'verse' 33 | ``` 34 | 35 | And then execute: 36 | 37 | ```bash 38 | $ bundle 39 | ``` 40 | 41 | Or install it yourself as: 42 | 43 | ```bash 44 | $ gem install verse 45 | ``` 46 | 47 | ## Contents 48 | 49 | * [1. Usage](#1-usage) 50 | * [1.1 Align](#11-align) 51 | * [1.2 Pad](#12-pad) 52 | * [1.3 Replace](#13-replace) 53 | * [1.4 Truncate](#14-truncate) 54 | * [1.5 Wrap](#15-wrap) 55 | 56 | ## 1 Usage 57 | 58 | ### 1.1 Align 59 | 60 | **Verse::Alignment** allows you to align text within a given length: 61 | 62 | ```ruby 63 | alignment = Verse::Alignment.new "for there is no folly of the beast\n" + 64 | " of the earth which\n" + 65 | " is not infinitely\n" + 66 | " outdone by the madness of men" 67 | ``` 68 | 69 | Then using direction out of `:right`, `:left` or `:center` methods and passing width you can align the text: 70 | 71 | ```ruby 72 | alignment.align(40, :right) # => 73 | " for there is no folly of the beast\n" + 74 | " of the earth which\n" + 75 | " is not infinitely\n" + 76 | " outdone by the madness of men" 77 | ``` 78 | 79 | Aligning `UTF-8` text is also supported: 80 | 81 | ```ruby 82 | alignment = Verse::Alignment.new "ラドクリフ\n" + 83 | "、マラソン五輪\n" + 84 | "代表に1万m出\n" + 85 | "場にも含み" 86 | 87 | alignment.center(20) # => 88 | " ラドクリフ \n" + 89 | " 、マラソン五輪 \n" + 90 | " 代表に1万m出 \n" + 91 | " 場にも含み " 92 | ``` 93 | 94 | **Verse::Alignment** works with ANSI escape codoes: 95 | 96 | ```ruby 97 | alignment = Verse::Alignment.new "\e[32mthe madness of men\e[0m" 98 | alignment.align(22, :center) 99 | # => " \e[32mthe madness of men\e[0m " 100 | ``` 101 | 102 | ### 1.2 Pad 103 | 104 | **Verse::Padding** provides facility to pad around text with a given padding. 105 | 106 | The padding value needs to be one of the following values corresponding with CSS padding property: 107 | 108 | ```ruby 109 | [1,1,1,1] # => pad text left & right with 1 character and add 1 line above & below 110 | [1,1] # => as above 111 | 1 # => as above 112 | ``` 113 | 114 | You can pad around a single line of text with `pad` method like so: 115 | 116 | ```ruby 117 | padding = Verse::Padding.new("Ignorance is the parent of fear.") 118 | 119 | padding.pad([1,1,1,1]) # => 120 | " \n" + 121 | " Ignorance is the parent of fear. \n" + 122 | " " 123 | ``` 124 | 125 | In addition, you can `pad` multiline content: 126 | 127 | ```ruby 128 | padding = Verse::Padding.new "It is the easiest thing\n" + 129 | "in the world for a man\n" + 130 | "to look as if he had \n" + 131 | "a great secret in him." 132 | 133 | padding.pad([1,1,1,1]) # => 134 | " \n" + 135 | " It is the easiest thing \n" + 136 | " in the world for a man \n" + 137 | " to look as if he had \n" + 138 | " a great secret in him. \n" + 139 | " " 140 | ``` 141 | 142 | You can also specify `UTF-8` text as well: 143 | 144 | ```ruby 145 | padding = Verse::Padding.new "ラドクリフ、マラソン" 146 | 147 | padding.pad([1,1,1,1]) # => 148 | " \n" + 149 | " ラドクリフ、マラソン \n" + 150 | " " 151 | ``` 152 | 153 | ### 1.3 Replace 154 | 155 | **Verse::Sanitizer** provides ability to sanitize text with unwanted characters. Given a text with line break characters, `replace` will remove or substitute all occurances of line breaks depending on surrounding context. 156 | 157 | ```ruby 158 | sanitizer = Verse::Sanitizer.new 159 | sanitizer.replace("It is not down on any map;\r\n true places never are.") 160 | # => "It is not down on any map; true places never are." 161 | ``` 162 | 163 | ### 1.4 Truncate 164 | 165 | Using **Verse::Truncation** you can truncate a given text after a given length. 166 | 167 | ```ruby 168 | truncation = Verse::Truncation.new "for there is no folly of the beast of the earth " + 169 | "which is not infinitely outdone by the madness of men" 170 | 171 | ``` 172 | 173 | Then to shorten the text to given length call `truncate`: 174 | 175 | ```ruby 176 | truncation.truncate(20) # => "for there is no fol…" 177 | ``` 178 | 179 | Pass in `:trailing` (by default `…`) to replace last characters: 180 | 181 | ```ruby 182 | truncation.truncate(22, trailing: '... (see more)') 183 | # => "for there...(see more)" 184 | ``` 185 | 186 | You can also specify `UTF-8` text as well: 187 | 188 | ```ruby 189 | truncation = Verse::Truncation.new 'ラドクリフ、マラソン五輪代表に1万m出場にも含み' 190 | truncation.truncate(12) # => "ラドクリフ…" 191 | ``` 192 | 193 | **Verse::Truncation** works with ANSI escape codoes: 194 | 195 | ```ruby 196 | truncation = Verse::Trucnation.new "I try \e[34mall things\e[0m, I achieve what I can" 197 | truncation.truncate(18) 198 | # => "I try \e[34mall things\e[0m…" 199 | ``` 200 | 201 | ### 1.5 Wrap 202 | 203 | **Verse::Wrapping** allows you to wrap text into lines no longer than `wrap_at` argument length. The `wrap` method will break either on whitespace character or in case of east Asian characters on character boundaries. 204 | 205 | ```ruby 206 | wrapping = Verse::Wrapping.new "Think not, is my eleventh commandment; " + 207 | "and sleep when you can, is my twelfth." 208 | 209 | ``` 210 | 211 | Then to wrap the text to given length do: 212 | 213 | ```ruby 214 | wrapping.wrap(30) # => 215 | "Think not, is my eleventh" 216 | "commandment; and sleep when" 217 | "you can, is my twelfth." 218 | ``` 219 | 220 | Similarly, to handle `UTF-8` text do: 221 | 222 | ```ruby 223 | wrapping = Verse::Wrapping.new "ラドクリフ、マラソン五輪代表に1万m出場にも含み" 224 | wrapping.wrap(8) # => 225 | "ラドクリ" 226 | "フ、マラ" 227 | "ソン五輪" 228 | "代表に1" 229 | "万m出場" 230 | "にも含み" 231 | ``` 232 | 233 | **Verse::Wrapping** knows how to handle ANSI codes: 234 | 235 | ```ruby 236 | wrapping = Verse::Wrapping.new "\e[32;44mIgnorance is the parent of fear.\e[0m" 237 | wrapping.wrap(14) # => 238 | "\e[32;44mIgnorance is \e[0m" 239 | "\e[32;44mthe parent of \e[0m" 240 | "\e[32;44mfear.\e[0m" 241 | ``` 242 | 243 | You can also call `wrap` directly on **Verse**: 244 | 245 | ```ruby 246 | Verse.wrap(text, wrap_at) 247 | ``` 248 | 249 | ## Contributing 250 | 251 | 1. Fork it ( https://github.com/piotrmurach/verse/fork ) 252 | 2. Create your feature branch (`git checkout -b my-new-feature`) 253 | 3. Commit your changes (`git commit -am 'Add some feature'`) 254 | 4. Push to the branch (`git push origin my-new-feature`) 255 | 5. Create a new Pull Request 256 | 257 | ## Copyright 258 | 259 | Copyright (c) 2015-2016 Piotr Murach. See LICENSE for further details. 260 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | FileList['tasks/**/*.rake'].each(&method(:import)) 6 | 7 | desc 'Run all specs' 8 | task ci: %w[ spec ] 9 | -------------------------------------------------------------------------------- /benchmarks/speed_profile.rb: -------------------------------------------------------------------------------- 1 | require 'benchmark/ips' 2 | require 'verse' 3 | 4 | text = "Ignorance is the parent of fear." 5 | 6 | Benchmark.ips do |x| 7 | x.report('wrap') do 8 | Verse::Wrapping.wrap(text, 10) 9 | end 10 | 11 | x.report('truncate') do 12 | Verse::Truncation.truncate(text, 10) 13 | end 14 | 15 | x.compare! 16 | end 17 | 18 | # Warming up -------------------------------------- 19 | # wrap 178.000 i/100ms 20 | # truncate 262.000 i/100ms 21 | # Calculating ------------------------------------- 22 | # wrap 1.812k (± 1.9%) i/s - 9.078k in 5.010776s 23 | # truncate 2.625k (± 1.9%) i/s - 13.362k in 5.092305s 24 | # Comparison: 25 | # truncate: 2624.9 i/s 26 | # wrap: 1812.3 i/s - 1.45x slower 27 | -------------------------------------------------------------------------------- /lib/verse.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'unicode/display_width' 4 | require 'unicode_utils/each_grapheme' 5 | 6 | require 'verse/alignment' 7 | require 'verse/padder' 8 | require 'verse/padding' 9 | require 'verse/sanitizer' 10 | require 'verse/truncation' 11 | require 'verse/wrapping' 12 | require 'verse/version' 13 | 14 | module Verse 15 | SPACE = ' '.freeze 16 | NEWLINE = "\n".freeze 17 | RESET = "\e[0m".freeze 18 | ANSI = "\033".freeze 19 | 20 | SPACE_RE = %r{\s+}mo.freeze 21 | NEWLINE_RE = %r{\n}o.freeze 22 | 23 | # Align a text to a given direction with the width 24 | # 25 | # @see Verse::Alignment#align 26 | # 27 | # @api public 28 | def self.align(text, width, direction, options = {}) 29 | Alignment.align(text, width, direction, options) 30 | end 31 | 32 | # Pad a text around with a given padding 33 | # 34 | # @see Verse::Padding#pad 35 | # 36 | # @api public 37 | def self.pad(text, padding, options = {}) 38 | Padding.pad(text, padding, options) 39 | end 40 | 41 | # Truncate a text at a given length 42 | # 43 | # @see Verse::Truncation#truncate 44 | # 45 | # @api public 46 | def self.truncate(text, truncate_at, options = {}) 47 | Truncation.truncate(text, truncate_at, options) 48 | end 49 | 50 | # Wrap a text into lines at wrap length 51 | # 52 | # @see Verse::Wrapping#wrap 53 | # 54 | # @api public 55 | def self.wrap(text, wrap_at, options = {}) 56 | Wrapping.wrap(text, wrap_at, options) 57 | end 58 | end # Verse 59 | -------------------------------------------------------------------------------- /lib/verse/alignment.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | # A class responsible for text alignment 5 | class Alignment 6 | attr_reader :fill 7 | 8 | attr_reader :direction 9 | 10 | # Initialize an Alignment 11 | # 12 | # @api public 13 | def initialize(text, options = {}) 14 | @text = text 15 | @fill = options.fetch(:fill) { SPACE } 16 | @direction = options.fetch(:direction) { :left } 17 | end 18 | 19 | # Aligns text to the left 20 | # 21 | # @return [String] 22 | # 23 | # @api public 24 | def left(width, options = {}) 25 | align(width, :left, options) 26 | end 27 | 28 | # Centers text within the width 29 | # 30 | # @return [String] 31 | # 32 | # @api public 33 | def center(width, options = {}) 34 | align(width, :center, options) 35 | end 36 | 37 | # Aligns text to the right 38 | # 39 | # @return [String] 40 | # 41 | # @api public 42 | def right(width, options = {}) 43 | align(width, :right, options) 44 | end 45 | 46 | # Align a text to a given direction with the width 47 | # 48 | # @see Verse::Alignment#align 49 | # 50 | # @api public 51 | def self.align(text, width, direction, options) 52 | new(text, options).align(width, direction, options) 53 | end 54 | 55 | # Aligns text within the width. 56 | # 57 | # If the text is greater than the width then unmodified 58 | # string is returned. 59 | # 60 | # @example 61 | # alignment = Verse::Alignment.new "the madness of men" 62 | # 63 | # alignment.align(22, :left) 64 | # # => "the madness of men " 65 | # 66 | # alignment.align(22, :center) 67 | # # => " the madness of men " 68 | # 69 | # alignment.align(22, :right) 70 | # # => " the madness of men" 71 | # 72 | # @api public 73 | def align(width, direction = :left, options = {}) 74 | return text unless width 75 | 76 | filler = options.fetch(:fill) { fill } 77 | method = convert_to_method(direction) 78 | process_lines { |line| send(method, line, width, filler) } 79 | end 80 | 81 | protected 82 | 83 | # The text to align 84 | # 85 | # @ api private 86 | attr_reader :text 87 | 88 | # @api private 89 | def convert_to_method(direction) 90 | case direction.to_sym 91 | when :left then :left_justify 92 | when :right then :right_justify 93 | when :center then :center_justify 94 | else 95 | raise ArgumentError, "Unknown alignment `#{direction}`." 96 | end 97 | end 98 | 99 | # @api private 100 | def process_lines 101 | lines = text.split(NEWLINE) 102 | return yield(text) if text.empty? 103 | lines.reduce([]) do |aligned, line| 104 | aligned << yield(line) 105 | end.join("\n") 106 | end 107 | 108 | # @api private 109 | def left_justify(text, width, filler) 110 | width_diff = width - display_width(text) 111 | if width_diff > 0 112 | text + filler * width_diff 113 | else 114 | text 115 | end 116 | end 117 | 118 | # @api private 119 | def right_justify(text, width, filler) 120 | width_diff = width - display_width(text) 121 | if width_diff > 0 122 | filler * width_diff + text 123 | else 124 | text 125 | end 126 | end 127 | 128 | # @api private 129 | def center_justify(text, width, filler) 130 | text_width = display_width(text) 131 | width_diff = width - text_width 132 | if width_diff > 0 133 | right_count = (width_diff.to_f / 2).ceil 134 | left_count = width_diff - right_count 135 | [filler * left_count, text, filler * right_count].join 136 | else 137 | text 138 | end 139 | end 140 | 141 | # @api private 142 | def display_width(text) 143 | Unicode::DisplayWidth.of(Sanitizer.sanitize(text)) 144 | end 145 | end # Alignment 146 | end # Verse 147 | -------------------------------------------------------------------------------- /lib/verse/padder.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | ParseError = Class.new(ArgumentError) 5 | 6 | # A class responsible for parsing padding value 7 | # 8 | # Used internally by {Verse::Padding} 9 | # 10 | # @api private 11 | class Padder 12 | # Padding 13 | # 14 | # @return [Array[Integer]] 15 | attr_reader :padding 16 | 17 | # Initialize a Padder 18 | # 19 | # @api public 20 | def initialize(padding) 21 | @padding = padding 22 | end 23 | 24 | # Parse padding options 25 | # 26 | # Turn possible values into 4 element array 27 | # 28 | # @example 29 | # padder = TTY::Table::Padder.parse(5) 30 | # padder.padding # => [5, 5, 5, 5] 31 | # 32 | # @param [Object] value 33 | # 34 | # @return [TTY::Padder] 35 | # the new padder with padding values 36 | # 37 | # @api public 38 | def self.parse(value = nil) 39 | return value if value.is_a?(self) 40 | 41 | new(convert_to_ary(value)) 42 | end 43 | 44 | # Convert value to 4 element array 45 | # 46 | # @return [Array[Integer]] 47 | # the 4 element padding array 48 | # 49 | # @api private 50 | def self.convert_to_ary(value) 51 | if value.class <= Numeric 52 | [value, value, value, value] 53 | elsif value.nil? 54 | [] 55 | elsif value.size == 2 56 | [value[0], value[1], value[0], value[1]] 57 | elsif value.size == 4 58 | value 59 | else 60 | fail ParseError, 'Wrong :padding parameter, must be an array' 61 | end 62 | end 63 | 64 | # Top padding 65 | # 66 | # @return [Integer] 67 | # 68 | # @api public 69 | def top 70 | @padding[0].to_i 71 | end 72 | 73 | # Set top padding 74 | # 75 | # @param [Integer] val 76 | # 77 | # @return [nil] 78 | # 79 | # @api public 80 | def top=(value) 81 | @padding[0] = value 82 | end 83 | 84 | # Right padding 85 | # 86 | # @return [Integer] 87 | # 88 | # @api public 89 | def right 90 | @padding[1].to_i 91 | end 92 | 93 | # Set right padding 94 | # 95 | # @param [Integer] val 96 | # 97 | # @api public 98 | def right=(value) 99 | @padding[1] = value 100 | end 101 | 102 | # Bottom padding 103 | # 104 | # @return [Integer] 105 | # 106 | # @api public 107 | def bottom 108 | @padding[2].to_i 109 | end 110 | 111 | # Set bottom padding 112 | # 113 | # @param [Integer] value 114 | # 115 | # @return [nil] 116 | # 117 | # @api public 118 | def bottom=(value) 119 | @padding[2] = value 120 | end 121 | 122 | # Left padding 123 | # 124 | # @return [Integer] 125 | # 126 | # @api public 127 | def left 128 | @padding[3].to_i 129 | end 130 | 131 | # Set left padding 132 | # 133 | # @param [Integer] value 134 | # 135 | # @return [nil] 136 | # 137 | # @api public 138 | def left=(value) 139 | @padding[3] = value 140 | end 141 | 142 | # Check if padding is set 143 | # 144 | # @return [Boolean] 145 | # 146 | # @api public 147 | def empty? 148 | padding.empty? 149 | end 150 | 151 | # Check if vertical padding is applied 152 | # 153 | # @return [Boolean] 154 | # 155 | # @api public 156 | def vertical? 157 | top.nonzero? || bottom.nonzero? 158 | end 159 | 160 | # Check if horizontal padding is applied 161 | # 162 | # @return [Boolean] 163 | # 164 | # @api public 165 | def horizontal? 166 | left.nonzero? || right.nonzero? 167 | end 168 | 169 | # String represenation of this padder with padding values 170 | # 171 | # @return [String] 172 | # 173 | # @api public 174 | def to_s 175 | inspect 176 | end 177 | end # Padder 178 | end # Verse 179 | -------------------------------------------------------------------------------- /lib/verse/padding.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | # A class responsible for text indentation 5 | class Padding 6 | # Initialize a Padding 7 | # 8 | # @api public 9 | def initialize(text, options = {}) 10 | @text = text 11 | @padding = Padder.parse(options[:padding]) 12 | end 13 | 14 | # Pad content out 15 | # 16 | # @see Verse::Padding#pad 17 | # 18 | # @api public 19 | def self.pad(text, padding, options) 20 | new(text, options).pad(padding, options) 21 | end 22 | 23 | # Apply padding to text 24 | # 25 | # @param [String] text 26 | # 27 | # @return [String] 28 | # 29 | # @api private 30 | def pad(padding = (not_set = true), options = {}) 31 | return text if @padding.empty? && not_set 32 | if !not_set 33 | @padding = Padder.parse(padding) 34 | end 35 | text_copy = text.dup 36 | column_width = maximum_length(text) 37 | elements = [] 38 | if @padding.top > 0 39 | elements << (SPACE * column_width + NEWLINE) * @padding.top 40 | end 41 | elements << text_copy 42 | if @padding.bottom > 0 43 | elements << (SPACE * column_width + NEWLINE) * @padding.bottom 44 | end 45 | elements.map { |el| pad_multi_line(el) }.join(NEWLINE) 46 | end 47 | 48 | protected 49 | 50 | # The text to pad 51 | # 52 | # @api private 53 | attr_reader :text 54 | 55 | # Apply padding to multi line text 56 | # 57 | # @param [String] text 58 | # 59 | # @return [String] 60 | # 61 | # @api private 62 | def pad_multi_line(text) 63 | text.split(NEWLINE).map { |part| pad_around(part) } 64 | end 65 | 66 | # Apply padding to left and right side of string 67 | # 68 | # @param [String] text 69 | # 70 | # @return [String] 71 | # 72 | # @api private 73 | def pad_around(text) 74 | text.insert(0, SPACE * @padding.left). 75 | insert(-1, SPACE * @padding.right) 76 | end 77 | 78 | # Determine maximum length for all multiline content 79 | # 80 | # @params [String] text 81 | # 82 | # @return [Integer] 83 | # 84 | # @api private 85 | def maximum_length(text) 86 | lines = text.split(/\n/, -1) 87 | display_width(lines.max_by { |line| display_width(line) } || '') 88 | end 89 | 90 | def display_width(string) 91 | Unicode::DisplayWidth.of(Sanitizer.sanitize(string)) 92 | end 93 | end # Padding 94 | end # Verse 95 | -------------------------------------------------------------------------------- /lib/verse/sanitizer.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | module Sanitizer 5 | ANSI_MATCHER = /(\[)?\033(\[)?[;?\d]*[\dA-Za-z](\])?/ 6 | 7 | LINE_BREAK = "(\r\n+|\r+|\n+|\t+)".freeze 8 | 9 | # Strip ANSI characters from the text 10 | # 11 | # @param [String] text 12 | # 13 | # @return [String] 14 | # 15 | # @api public 16 | def sanitize(text) 17 | text.gsub(ANSI_MATCHER, '') 18 | end 19 | module_function :sanitize 20 | 21 | # Check if string is an ANSI code 22 | # 23 | # @param [String] string 24 | # the string to check 25 | # 26 | # @return [Boolean] 27 | # 28 | # @api public 29 | def ansi?(string) 30 | !!(string =~ /^(\[)?\033(\[)?[;?\d]*[\dA-Za-z]([\];])?$/) 31 | end 32 | module_function :ansi? 33 | 34 | # Replace separator with whitespace 35 | # 36 | # @example 37 | # replace(" \n ") # => " " 38 | # replace("\n") # => " " 39 | # 40 | # @param [String] text 41 | # 42 | # @param [String] separator 43 | # 44 | # @return [String] 45 | # 46 | # @api public 47 | def replace(text, separator = LINE_BREAK) 48 | text.gsub(/([ ]+)#{separator}/, "\\1") 49 | .gsub(/#{separator}(?[ ]+)/, "\\k") 50 | .gsub(/#{separator}/, ' ') 51 | end 52 | module_function :replace 53 | end # Sanitizer 54 | end # Verse 55 | -------------------------------------------------------------------------------- /lib/verse/truncation.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | # A class responsible for text truncation operations 5 | class Truncation 6 | DEFAULT_TRAILING = '…'.freeze 7 | 8 | DEFAULT_LENGTH = 30 9 | 10 | attr_reader :separator 11 | 12 | attr_reader :trailing 13 | 14 | # Initialize a Truncation 15 | # 16 | # @param [String] text 17 | # the text to be truncated 18 | # 19 | # @param [Hash] options 20 | # @option options [Symbol] :separator the character for splitting words 21 | # @option options [Symbol] :trailing the character for ending sentence 22 | # 23 | # @api public 24 | def initialize(text, options = {}) 25 | @text = text.dup.freeze 26 | @separator = options.fetch(:separator) { nil } 27 | @trailing = options.fetch(:trailing) { DEFAULT_TRAILING } 28 | end 29 | 30 | # Truncate a text at a given length 31 | # 32 | # @see Verse::Truncation#truncate 33 | # 34 | # @api public 35 | def self.truncate(text, truncate_at, options = {}) 36 | new(text, options).truncate(truncate_at, options) 37 | end 38 | 39 | # Truncate a text at a given length (defualts to 30) 40 | # 41 | # @example 42 | # truncation = Verse::Truncation.new 43 | # "The sovereignest thing on earth is parmacetti for an inward bruise." 44 | # 45 | # truncation.truncate 46 | # # => "The sovereignest thing on ear…" 47 | # 48 | # truncate(20) 49 | # # => "The sovereignest th…" 50 | # 51 | # truncate(20, separator: ' ' ) 52 | # # => "The sovereignest…" 53 | # 54 | # truncate(40, trailing: '... (see more)' ) 55 | # # => "The sovereignest thing on... (see more)" 56 | # 57 | # @api public 58 | def truncate(truncate_at = DEFAULT_LENGTH, options = {}) 59 | if display_width(text) <= truncate_at.to_i || truncate_at.to_i.zero? 60 | return text.dup 61 | end 62 | trail = options.fetch(:trailing) { trailing } 63 | separation = options.fetch(:separator) { separator } 64 | width = display_width(text) 65 | sanitized_text = Sanitizer.sanitize(text) 66 | 67 | return text if width <= truncate_at 68 | 69 | length_without_trailing = truncate_at - display_width(trail) 70 | chars = to_chars(sanitized_text).to_a 71 | stop = chars[0, length_without_trailing].rindex(separation) 72 | slice_length = stop || length_without_trailing 73 | sliced_chars = chars[0, slice_length] 74 | original_chars = to_chars(text).to_a[0, 3 * slice_length] 75 | shorten(original_chars, sliced_chars, length_without_trailing).join + trail 76 | end 77 | 78 | protected 79 | 80 | attr_reader :text 81 | 82 | # Perform actual shortening of the text 83 | # 84 | # @return [String] 85 | # 86 | # @api private 87 | def shorten(original_chars, chars, length_without_trailing) 88 | truncated = [] 89 | char_width = display_width(chars[0]) 90 | while length_without_trailing - char_width > 0 91 | orig_char = original_chars.shift 92 | char = chars.shift 93 | break unless char 94 | while orig_char != char # consume ansi 95 | ansi = true 96 | truncated << orig_char 97 | orig_char = original_chars.shift 98 | end 99 | truncated << char 100 | char_width = display_width(char) 101 | length_without_trailing -= char_width 102 | end 103 | truncated << ["\e[0m"] if ansi 104 | truncated 105 | end 106 | 107 | # @api private 108 | def to_chars(text) 109 | UnicodeUtils.each_grapheme(text) 110 | end 111 | 112 | # @api private 113 | def display_width(string) 114 | Unicode::DisplayWidth.of(Sanitizer.sanitize(string)) 115 | end 116 | end # Truncation 117 | end # Verse 118 | -------------------------------------------------------------------------------- /lib/verse/version.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | VERSION = '0.5.0' 5 | end # Verse 6 | -------------------------------------------------------------------------------- /lib/verse/wrapping.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | module Verse 4 | # A class responsible for text wrapping 5 | class Wrapping 6 | DEFAULT_WIDTH = 80 7 | 8 | # Initialize a Wrapping 9 | # 10 | # @param [String] text 11 | # the text to be wrapped 12 | # 13 | # @param [Hash] options 14 | # @option options [Symbol] :padding the desired spacing 15 | # 16 | # @api public 17 | def initialize(text, options = {}) 18 | @text = text 19 | @line_width = options.fetch(:line_width) { DEFAULT_WIDTH } 20 | end 21 | 22 | # Wrap a text into lines no longer than wrap_at 23 | # 24 | # @api public 25 | def self.wrap(text, wrap_at, options = {}) 26 | new(text, options).wrap(wrap_at) 27 | end 28 | 29 | # Wrap a text into lines no longer than wrap_at length. 30 | # Preserves existing lines and existing word boundaries. 31 | # 32 | # @example 33 | # wrapping = Verse::Wrapping.new "Some longish text" 34 | # 35 | # wrapping.wrap(8) 36 | # # => >Some 37 | # >longish 38 | # >text 39 | # 40 | # @api public 41 | def wrap(wrap_at = DEFAULT_WIDTH) 42 | if text.length < wrap_at.to_i || wrap_at.to_i.zero? 43 | return text 44 | end 45 | ansi_stack = [] 46 | text.split(NEWLINE, -1).map do |paragraph| 47 | format_paragraph(paragraph, wrap_at, ansi_stack) 48 | end * NEWLINE 49 | end 50 | 51 | protected 52 | 53 | # The text to wrap 54 | # 55 | # @api private 56 | attr_reader :text 57 | 58 | # Format paragraph to be maximum of wrap_at length 59 | # 60 | # @param [String] paragraph 61 | # the paragraph to format 62 | # @param [Integer] wrap_at 63 | # the maximum length to wrap the paragraph 64 | # 65 | # @return [Array[String]] 66 | # the wrapped lines 67 | # 68 | # @api private 69 | def format_paragraph(paragraph, wrap_at, ansi_stack) 70 | cleared_para = Sanitizer.replace(paragraph) 71 | lines = [] 72 | line = '' 73 | word = '' 74 | word_length = 0 75 | line_length = 0 76 | char_length = 0 # visible char length 77 | text_length = display_width(cleared_para) 78 | total_length = 0 79 | ansi = '' 80 | matched = nil 81 | to_chars(cleared_para) do |char| 82 | if char == ANSI # found ansi 83 | ansi << char && next 84 | end 85 | 86 | if ansi.length > 0 87 | ansi << char 88 | if Sanitizer.ansi?(ansi) # we found ansi let's consume 89 | matched = ansi 90 | elsif matched 91 | ansi_stack << [matched[0...-1], line_length + word_length] 92 | matched = nil 93 | ansi = '' 94 | end 95 | next if ansi.length > 0 96 | end 97 | 98 | char_length = display_width(char) 99 | total_length += char_length 100 | if line_length + word_length + char_length <= wrap_at 101 | if char == SPACE || total_length == text_length 102 | line << word + char 103 | line_length += word_length + char_length 104 | word = '' 105 | word_length = 0 106 | else 107 | word << char 108 | word_length += char_length 109 | end 110 | next 111 | end 112 | 113 | if char == SPACE # ends with space 114 | lines << insert_ansi(ansi_stack, line) 115 | line = '' 116 | line_length = 0 117 | word += char 118 | word_length += char_length 119 | elsif word_length + char_length <= wrap_at 120 | lines << insert_ansi(ansi_stack, line) 121 | line = word + char 122 | line_length = word_length + char_length 123 | word = '' 124 | word_length = 0 125 | else # hyphenate word - too long to fit a line 126 | lines << insert_ansi(ansi_stack, word) 127 | line_length = 0 128 | word = char 129 | word_length = char_length 130 | end 131 | end 132 | lines << insert_ansi(ansi_stack, line) unless line.empty? 133 | lines << insert_ansi(ansi_stack, word) unless word.empty? 134 | lines 135 | end 136 | 137 | # Insert ANSI code into string 138 | # 139 | # Check if there are any ANSI states, if present 140 | # insert ANSI codes at given positions unwinding the stack. 141 | # 142 | # @param [Array[Array[String, Integer]]] ansi_stack 143 | # the ANSI codes to apply 144 | # 145 | # @param [String] string 146 | # the string to insert ANSI codes into 147 | # 148 | # @return [String] 149 | # 150 | # @api private 151 | def insert_ansi(ansi_stack, string) 152 | return string if ansi_stack.empty? 153 | to_remove = 0 154 | reset_index = -1 155 | output = string.dup 156 | resetting = false 157 | ansi_stack.reverse_each do |state| 158 | if state[0] =~ /#{Regexp.quote(RESET)}/ 159 | resetting = true 160 | reset_index = state[1] 161 | to_remove += 2 162 | next 163 | elsif !resetting 164 | reset_index = -1 165 | resetting = false 166 | end 167 | 168 | color, color_index = *state 169 | output.insert(reset_index, RESET).insert(color_index, color) 170 | end 171 | ansi_stack.pop(to_remove) # remove used states 172 | output 173 | end 174 | 175 | # @api private 176 | def to_chars(text, &block) 177 | if block_given? 178 | UnicodeUtils.each_grapheme(text, &block) 179 | else 180 | UnicodeUtils.each_grapheme(text) 181 | end 182 | end 183 | 184 | # Visible width of string 185 | # 186 | # @api private 187 | def display_width(string) 188 | Unicode::DisplayWidth.of(Sanitizer.sanitize(string)) 189 | end 190 | end # Wrapping 191 | end # Verse 192 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | if RUBY_VERSION > '1.9' and (ENV['COVERAGE'] || ENV['TRAVIS']) 4 | require 'simplecov' 5 | require 'coveralls' 6 | 7 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 8 | SimpleCov::Formatter::HTMLFormatter, 9 | Coveralls::SimpleCov::Formatter 10 | ] 11 | 12 | SimpleCov.start do 13 | command_name 'spec' 14 | add_filter 'spec' 15 | end 16 | end 17 | 18 | require 'verse' 19 | 20 | RSpec.configure do |config| 21 | config.expect_with :rspec do |expectations| 22 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 23 | end 24 | 25 | config.mock_with :rspec do |mocks| 26 | mocks.verify_partial_doubles = true 27 | end 28 | 29 | # Limits the available syntax to the non-monkey patched syntax that is recommended. 30 | config.disable_monkey_patching! 31 | 32 | # This setting enables warnings. It's recommended, but in some cases may 33 | # be too noisy due to issues in dependencies. 34 | config.warnings = true 35 | 36 | if config.files_to_run.one? 37 | config.default_formatter = 'doc' 38 | end 39 | 40 | config.profile_examples = 2 41 | 42 | config.order = :random 43 | 44 | Kernel.srand config.seed 45 | end 46 | 47 | def unindent(text) 48 | text.gsub(/^[ \t]*/, '').chomp 49 | end 50 | -------------------------------------------------------------------------------- /spec/unit/align_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse, '#align' do 6 | it "aligns text" do 7 | text = "the madness of men" 8 | expect(Verse.align(text, 22, :center)).to eq(" the madness of men ") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/alignment/align_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Alignment, '.align' do 6 | 7 | it "doesn't align unrecognized direction" do 8 | text = "the madness of men" 9 | alignment = Verse::Alignment.new(text) 10 | expect { 11 | alignment.align(22, :unknown) 12 | }.to raise_error(ArgumentError, /Unknown alignment/) 13 | end 14 | 15 | it "fills empty" do 16 | alignment = Verse::Alignment.new('') 17 | expect(alignment.center(22)).to eq(" ") 18 | end 19 | 20 | it "centers line" do 21 | text = "the madness of men" 22 | alignment = Verse::Alignment.new(text) 23 | expect(alignment.center(22)).to eq(" the madness of men ") 24 | end 25 | 26 | it "centers utf line" do 27 | text = "こんにちは" 28 | alignment = Verse::Alignment.new(text) 29 | expect(alignment.center(20)).to eq(" こんにちは ") 30 | end 31 | 32 | it "centers ansi line" do 33 | text = "\e[32mthe madness of men\e[0m" 34 | alignment = Verse::Alignment.new(text) 35 | expect(alignment.center(22)).to eq(" \e[32mthe madness of men\e[0m ") 36 | end 37 | 38 | it "centers multiline text" do 39 | text = "for there is no folly of the beast\nof the earth which\nis not infinitely\noutdone by the madness of men" 40 | alignment = Verse::Alignment.new(text) 41 | expect(alignment.center(40)).to eq([ 42 | " for there is no folly of the beast \n", 43 | " of the earth which \n", 44 | " is not infinitely \n", 45 | " outdone by the madness of men " 46 | ].join) 47 | end 48 | 49 | it "centers multiline utf text" do 50 | text = "ラドクリフ\n、マラソン五輪\n代表に1万m出\n場にも含み" 51 | alignment = Verse::Alignment.new(text) 52 | expect(alignment.center(20)).to eq([ 53 | " ラドクリフ \n", 54 | " 、マラソン五輪 \n", 55 | " 代表に1万m出 \n", 56 | " 場にも含み " 57 | ].join) 58 | end 59 | 60 | it "centers ansi text" do 61 | text = "for \e[35mthere\e[0m is no folly of the beast\nof the \e[33mearth\e0m which\nis \e[34mnot infinitely\e[0m\n\e[33moutdone\e[0m by the madness of men" 62 | alignment = Verse::Alignment.new(text) 63 | expect(alignment.center(40)).to eq([ 64 | " for \e[35mthere\e[0m is no folly of the beast \n", 65 | " of the \e[33mearth\e0m which \n", 66 | " is \e[34mnot infinitely\e[0m \n", 67 | " \e[33moutdone\e[0m by the madness of men " 68 | ].join) 69 | end 70 | 71 | it "centers multiline text with fill of '*'" do 72 | text = "for there is no folly of the beast\nof the earth which\nis not infinitely\noutdone by the madness of men" 73 | alignment = Verse::Alignment.new(text, fill: '*') 74 | expect(alignment.center(40)).to eq([ 75 | "***for there is no folly of the beast***\n", 76 | "***********of the earth which***********\n", 77 | "***********is not infinitely************\n", 78 | "*****outdone by the madness of men******" 79 | ].join) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/unit/alignment/left_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Alignment, '.left' do 6 | it "aligns line to left" do 7 | text = "the madness of men" 8 | alignment = Verse::Alignment.new(text) 9 | expect(alignment.left(22)).to eq("the madness of men ") 10 | end 11 | 12 | it "fills empty" do 13 | alignment = Verse::Alignment.new('') 14 | expect(alignment.left(22)).to eq(" ") 15 | end 16 | 17 | it "left justifies utf line" do 18 | text = "こんにちは" 19 | alignment = Verse::Alignment.new(text) 20 | expect(alignment.align(20, :left)).to eq("こんにちは ") 21 | end 22 | 23 | it "left justifies ansi line" do 24 | text = "\e[32mthe madness of men\e[0m" 25 | alignment = Verse::Alignment.new(text) 26 | expect(alignment.align(22, :left)).to eq("\e[32mthe madness of men\e[0m ") 27 | end 28 | 29 | it "aligns multiline text to left" do 30 | text = "for there is no folly of the beast\nof the earth which\nis not infinitely\noutdone by the madness of men" 31 | alignment = Verse::Alignment.new(text) 32 | expect(alignment.left(40)).to eq([ 33 | "for there is no folly of the beast \n", 34 | "of the earth which \n", 35 | "is not infinitely \n", 36 | "outdone by the madness of men " 37 | ].join) 38 | end 39 | 40 | it "left justifies multiline utf text" do 41 | text = "ラドクリフ\n、マラソン五輪\n代表に1万m出\n場にも含み" 42 | alignment = Verse::Alignment.new(text) 43 | expect(alignment.left(20)).to eq([ 44 | "ラドクリフ \n", 45 | "、マラソン五輪 \n", 46 | "代表に1万m出 \n", 47 | "場にも含み " 48 | ].join) 49 | end 50 | 51 | it "left justifies ansi text" do 52 | text = "for \e[35mthere\e[0m is no folly of the beast\nof the \e[33mearth\e0m which\nis \e[34mnot infinitely\e[0m\n\e[33moutdone\e[0m by the madness of men" 53 | alignment = Verse::Alignment.new(text) 54 | expect(alignment.left(40)).to eq([ 55 | "for \e[35mthere\e[0m is no folly of the beast \n", 56 | "of the \e[33mearth\e0m which \n", 57 | "is \e[34mnot infinitely\e[0m \n", 58 | "\e[33moutdone\e[0m by the madness of men " 59 | ].join) 60 | end 61 | 62 | it "left justifies multiline text with fill of '*'" do 63 | text = "for there is no folly of the beast\nof the earth which\nis not infinitely\noutdone by the madness of men" 64 | alignment = Verse::Alignment.new(text, fill: '*') 65 | expect(alignment.left(40)).to eq([ 66 | "for there is no folly of the beast******\n", 67 | "of the earth which**********************\n", 68 | "is not infinitely***********************\n", 69 | "outdone by the madness of men***********" 70 | ].join) 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/unit/alignment/right_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Alignment, '.right' do 6 | it "aligns line to left" do 7 | text = "the madness of men" 8 | alignment = Verse::Alignment.new(text) 9 | expect(alignment.right(22)).to eq(" the madness of men") 10 | end 11 | 12 | it "fills empty" do 13 | alignment = Verse::Alignment.new('') 14 | expect(alignment.left(22)).to eq(" ") 15 | end 16 | 17 | it "right justifies utf line" do 18 | text = "こんにちは" 19 | alignment = Verse::Alignment.new(text) 20 | expect(alignment.align(20, :right)).to eq(" こんにちは") 21 | end 22 | 23 | it "right justifies utf line" do 24 | text = "\e[32mthe madness of men\e[0m" 25 | alignment = Verse::Alignment.new(text) 26 | expect(alignment.align(22, :right)).to eq(" \e[32mthe madness of men\e[0m") 27 | end 28 | 29 | it "aligns multiline text to left" do 30 | text = "for there is no folly of the beast\n of the earth which\n is not infinitely\n outdone by the madness of men" 31 | alignment = Verse::Alignment.new(text) 32 | expect(alignment.right(40)).to eq([ 33 | " for there is no folly of the beast\n", 34 | " of the earth which\n", 35 | " is not infinitely\n", 36 | " outdone by the madness of men" 37 | ].join) 38 | end 39 | 40 | it "right justifies multiline utf text" do 41 | text = "ラドクリフ\n、マラソン五輪\n代表に1万m出\n場にも含み" 42 | alignment = Verse::Alignment.new(text) 43 | expect(alignment.right(20)).to eq([ 44 | " ラドクリフ\n", 45 | " 、マラソン五輪\n", 46 | " 代表に1万m出\n", 47 | " 場にも含み" 48 | ].join) 49 | end 50 | 51 | it "right justfies ansi text" do 52 | text = "for \e[35mthere\e[0m is no folly of the beast\nof the \e[33mearth\e0m which\nis \e[34mnot infinitely\e[0m\n\e[33moutdone\e[0m by the madness of men" 53 | alignment = Verse::Alignment.new(text) 54 | expect(alignment.right(40)).to eq([ 55 | " for \e[35mthere\e[0m is no folly of the beast\n", 56 | " of the \e[33mearth\e0m which\n", 57 | " is \e[34mnot infinitely\e[0m\n", 58 | " \e[33moutdone\e[0m by the madness of men" 59 | ].join) 60 | end 61 | 62 | it "right justifies multiline text with fill of '*'" do 63 | text = "for there is no folly of the beast\nof the earth which\nis not infinitely\noutdone by the madness of men" 64 | alignment = Verse::Alignment.new(text, fill: '*') 65 | expect(alignment.right(40)).to eq([ 66 | "******for there is no folly of the beast\n", 67 | "**********************of the earth which\n", 68 | "***********************is not infinitely\n", 69 | "***********outdone by the madness of men" 70 | ].join) 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/unit/pad_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse, '#pad' do 6 | it "pads text" do 7 | text = 'ラドクリフ、マラソン五輪代表に1万m出場にも含み' 8 | expect(Verse.pad(text, [1,1,1,1])).to eql([ 9 | ' ', 10 | ' ラドクリフ、マラソン五輪代表に1万m出場にも含み ', 11 | ' ' 12 | ].join("\n")) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/unit/padder/accessors_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Padder, 'accessors' do 6 | subject(:padder) { described_class } 7 | 8 | it "allows to set padding through accessors" do 9 | instance = padder.parse([1,2,3,4]) 10 | instance.top = 5 11 | expect(instance.padding).to eq([5,2,3,4]) 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /spec/unit/padder/parse_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Padder, '#parse' do 6 | subject(:padder) { described_class } 7 | 8 | it "parses nil" do 9 | instance = padder.parse(nil) 10 | expect(instance.padding).to eq([]) 11 | end 12 | 13 | it 'parses self' do 14 | value = described_class.new([]) 15 | instance = padder.parse(value) 16 | expect(instance.padding).to eq([]) 17 | end 18 | 19 | it "parses digit" do 20 | instance = padder.parse(5) 21 | expect(instance.padding).to eq([5,5,5,5]) 22 | end 23 | 24 | it "parses 2-element array" do 25 | instance = padder.parse([2,3]) 26 | expect(instance.padding).to eq([2,3,2,3]) 27 | end 28 | 29 | it "parses 4-element array" do 30 | instance = padder.parse([1,2,3,4]) 31 | expect(instance.padding).to eq([1,2,3,4]) 32 | end 33 | 34 | it "fails to parse unknown value" do 35 | expect { 36 | padder.parse(:unknown) 37 | }.to raise_error(Verse::ParseError) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/padding/pad_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Padding, '.pad' do 6 | it "doesn't pad without padding option" do 7 | text = "Ignorance is the parent of fear." 8 | padding = Verse::Padding.new(text) 9 | expect(padding.pad).to eq(text) 10 | end 11 | 12 | it "pads content with specific padding" do 13 | text = "Ignorance is the parent of fear." 14 | padding = Verse::Padding.new(text) 15 | expect(padding.pad([1,1,1,1])).to eq([ 16 | " ", 17 | " Ignorance is the parent of fear. ", 18 | " ", 19 | ].join("\n")) 20 | end 21 | 22 | it "pads content with global padding" do 23 | text = "Ignorance is the parent of fear." 24 | padding = Verse::Padding.new(text, padding: [1,1,1,1]) 25 | expect(padding.pad).to eq([ 26 | " ", 27 | " Ignorance is the parent of fear. ", 28 | " ", 29 | ].join("\n")) 30 | end 31 | 32 | it "pads unicode content" do 33 | text = "ラドクリフ、マラソン" 34 | padding = Verse::Padding.new(text) 35 | expect(padding.pad([1,1,1,1])).to eq([ 36 | " ", 37 | " ラドクリフ、マラソン ", 38 | " " 39 | ].join("\n")) 40 | end 41 | 42 | it "pads multiline content" do 43 | text = "It is the easiest thing\nin the world for a man\nto look as if he had \na great secret in him." 44 | padding = Verse::Padding.new(text, padding: [1,1,1,1]) 45 | expect(padding.pad()).to eq([ 46 | " ", 47 | " It is the easiest thing ", 48 | " in the world for a man ", 49 | " to look as if he had ", 50 | " a great secret in him. ", 51 | " ", 52 | ].join("\n")) 53 | end 54 | 55 | it "pads ANSI codes inside content" do 56 | text = "It is \e[35mthe easiest\e[0m thing\nin the \e[34mworld\e[0m for a man\nto look as if he had \na great \e[33msecret\e[0m in him." 57 | padding = Verse::Padding.new(text, padding: [1,1,1,1]) 58 | expect(padding.pad()).to eq([ 59 | " ", 60 | " It is \e[35mthe easiest\e[0m thing ", 61 | " in the \e[34mworld\e[0m for a man ", 62 | " to look as if he had ", 63 | " a great \e[33msecret\e[0m in him. ", 64 | " ", 65 | ].join("\n")) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/unit/sanitizer/ansi_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Sanitizer, '.ansi?' do 6 | subject(:sanitizer) { described_class } 7 | 8 | it "checks if code is ansi" do 9 | expect(sanitizer.ansi?("\e[0;33m")).to eq(true) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/unit/sanitizer/replace_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Sanitizer, '.replace' do 6 | subject(:sanitizer) { described_class } 7 | 8 | { 9 | " \n" => ' ', 10 | "\n " => ' ', 11 | "\n" => ' ', 12 | "\n\n\n" => ' ', 13 | " \n " => ' ', 14 | " \n \n \n" => ' ' 15 | }.each do |string, expected| 16 | it "replaces '#{string.gsub(/\n/, '\\n')}' with whitespace" do 17 | expect(sanitizer.replace(string)).to eq(expected) 18 | end 19 | end 20 | 21 | { 22 | " \r\n" => ' ', 23 | "\r\n " => ' ', 24 | "\r\n" => ' ', 25 | " \r\n " => ' ', 26 | }.each do |string, expected| 27 | it "replaces '#{string.gsub(/\r\n/, '\\r\\n')}' with whitespace" do 28 | expect(sanitizer.replace(string, "\r\n")).to eq(expected) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/sanitizer/sanitize_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Sanitizer, '.sanitize' do 6 | subject(:sanitizer) { described_class } 7 | 8 | { 9 | "\e[20h" => '', 10 | "\e[?1h" => '', 11 | "\e[20l" => '', 12 | "\e[?9l" => '', 13 | "\eO" => '', 14 | "\e[m" => '', 15 | "\e[0m" => '', 16 | "\eA" => '', 17 | "\e[0;33;49;3;9;4m\e[0m" => '' 18 | }.each do |code, expected| 19 | it "strips #{code} from string" do 20 | expect(sanitizer.sanitize(code)).to eq(expected) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/unit/truncate_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse, '#truncate' do 6 | it "truncates text" do 7 | text = 'ラドクリフ、マラソン五輪代表に1万m出場にも含み' 8 | expect(Verse.truncate(text, 12)).to eq('ラドクリフ…') 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/truncation/new_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Truncation, '#new' do 6 | let(:text) { 'There go the ships; there is that Leviathan whom thou hast made to play therein.'} 7 | 8 | it "defaults to no separator and unicode trailing" do 9 | truncation = Verse::Truncation.new text 10 | expect(truncation.separator).to eq(nil) 11 | end 12 | 13 | it "defaults to unicode trailing" do 14 | truncation = Verse::Truncation.new text 15 | expect(truncation.trailing).to eq('…') 16 | end 17 | 18 | 19 | it "allows to setup global separator value" do 20 | truncation = Verse::Truncation.new text, separator: ' ' 21 | expect(truncation.separator).to eq(' ') 22 | end 23 | 24 | it "allows to setup global trailing value" do 25 | truncation = Verse::Truncation.new text, trailing: '...' 26 | expect(truncation.trailing).to eq('...') 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/truncation/truncate_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Truncation, '.truncate' do 6 | let(:text) { 'ラドクリフ、マラソン五輪代表に1万m出場にも含み' } 7 | 8 | it "doesn't change text for 0 length" do 9 | truncation = Verse::Truncation.new(text) 10 | expect(truncation.truncate(0)).to eq(text) 11 | end 12 | 13 | it "doensn't change text for nil length" do 14 | truncation = Verse::Truncation.new(text) 15 | expect(truncation.truncate(nil)).to eq(text) 16 | end 17 | 18 | it "doesn't change text for equal length" do 19 | truncation = Verse::Truncation.new(text) 20 | expect(truncation.truncate(text.length * 2)).to eq(text) 21 | end 22 | 23 | it 'truncates text' do 24 | truncation = Verse::Truncation.new(text) 25 | trailing = '…' 26 | expect(truncation.truncate(12)).to eq("ラドクリフ#{trailing}") 27 | end 28 | 29 | it "estimates total width correctly " do 30 | truncation = Verse::Truncation.new('太丸ゴシック体') 31 | trailing = '…' 32 | expect(truncation.truncate(8)).to eq("太丸ゴ#{trailing}") 33 | end 34 | 35 | it "doesn't truncate text when length exceeds content" do 36 | truncation = Verse::Truncation.new(text) 37 | expect(truncation.truncate(100)).to eq(text) 38 | end 39 | 40 | it 'truncates text with string separator' do 41 | truncation = Verse::Truncation.new(text) 42 | trailing = '…' 43 | expect(truncation.truncate(12, separator: '')).to eq("ラドクリフ#{trailing}") 44 | end 45 | 46 | it 'truncates text with regex separator' do 47 | truncation = Verse::Truncation.new(text) 48 | trailing = '…' 49 | expect(truncation.truncate(12, separator: /\s/)).to eq("ラドクリフ#{trailing}") 50 | end 51 | 52 | it 'truncates text with custom trailing' do 53 | truncation = Verse::Truncation.new(text) 54 | trailing = '... (see more)' 55 | expect(truncation.truncate(20, trailing: trailing)).to eq("ラド#{trailing}") 56 | end 57 | 58 | it 'correctly truncates with ANSI characters' do 59 | text = "I try \e[34mall things\e[0m, I achieve what I can" 60 | truncation = Verse::Truncation.new(text) 61 | expect(truncation.truncate(18)).to eq("I try \e[34mall things\e[0m…") 62 | end 63 | 64 | it "finishes on word boundary" do 65 | text = "for there is no folly of the beast of the earth" 66 | truncation = Verse::Truncation.new(text) 67 | expect(truncation.truncate(20, separator: ' ')).to eq('for there is no…') 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/unit/wrap_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse, '#wrap' do 6 | it "wraps text" do 7 | text = 'ラドクリフ、マラソン五輪代表に1万m出場にも含み' 8 | expect(Verse.wrap(text, 8)).to eql([ 9 | "ラドクリ", 10 | "フ、マラ", 11 | "ソン五輪", 12 | "代表に1", 13 | "万m出場", 14 | "にも含み" 15 | ].join("\n")) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/unit/wrapping/wrap_spec.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe Verse::Wrapping, '.wrap' do 6 | context 'when unicode' do 7 | let(:text) { 'ラドクリフ、マラソン五輪代表に1万m出場にも含み' } 8 | 9 | it "doesn't wrap at zero length" do 10 | wrapping = Verse::Wrapping.new(text) 11 | expect(wrapping.wrap(0)).to eq(text) 12 | end 13 | 14 | it "doesn't wrap at nil length" do 15 | wrapping = Verse::Wrapping.new(text) 16 | expect(wrapping.wrap(nil)).to eq(text) 17 | end 18 | 19 | it "doesn't wrap at length exceeding content length" do 20 | wrapping = Verse::Wrapping.new(text) 21 | expect(wrapping.wrap(100)).to eq(text) 22 | end 23 | 24 | it "wraps correctly unbreakable words" do 25 | wrapping = Verse::Wrapping.new('foobar1') 26 | expect(wrapping.wrap(3)).to eq([ 27 | "foo", 28 | "bar", 29 | "1" 30 | ].join("\n")) 31 | end 32 | 33 | it "preserves newlines" do 34 | text = "It is not down\n on any map;\n true places never are." 35 | wrapping = Verse::Wrapping.new(text) 36 | expect(wrapping.wrap(10)).to eq([ 37 | "It is not ", 38 | "down", 39 | " on any ", 40 | "map;", 41 | " true ", 42 | "places ", 43 | "never are." 44 | ].join("\n")) 45 | end 46 | 47 | 48 | it "wraps ascii text" do 49 | text = "for there is no folly of the beast of the earth which is not infinitely outdone by the madness of men " 50 | wrapping = Verse::Wrapping.new(text) 51 | expect(wrapping.wrap(16)).to eq([ 52 | "for there is no ", 53 | "folly of the ", 54 | "beast of the ", 55 | "earth which is ", 56 | "not infinitely ", 57 | "outdone by the ", 58 | "madness of men " 59 | ].join("\n")) 60 | end 61 | 62 | it 'wraps at 8 characters' do 63 | wrapping = Verse::Wrapping.new(text) 64 | expect(wrapping.wrap(8)).to eq([ 65 | "ラドクリ", 66 | "フ、マラ", 67 | "ソン五輪", 68 | "代表に1", 69 | "万m出場", 70 | "にも含み" 71 | ].join("\n")) 72 | end 73 | 74 | it 'preserves whitespace' do 75 | # text = " As for me, I am tormented" 76 | text = " As for me, I am tormented with an everlasting itch for things remote. " 77 | wrapping = Verse::Wrapping.new(text) 78 | expect(wrapping.wrap(10)).to eq([ 79 | " As for ", 80 | "me, I ", 81 | "am ", 82 | "tormented ", 83 | "with an ", 84 | "e", 85 | "verlasting", 86 | " itch ", 87 | "for ", 88 | "things ", 89 | "remote. " 90 | ].join("\n")) 91 | end 92 | end 93 | 94 | context 'when long text' do 95 | it "wraps long text at 45 characters" do 96 | text = 97 | "What of it, if some old hunks of a sea-captain orders me to get a broom and sweep down the decks? What does that indignity amount to, weighed, I mean, in the scales of the New Testament? Do you think the archangel Gabriel thinks anything the less of me, because I promptly and respectfully obey that old hunks in that particular instance? Who ain't a slave? Tell me that. Well, then, however the old sea-captains may order me about--however they may thump and punch me about, I have the satisfaction of knowing that it is all right;" 98 | wrapping = Verse::Wrapping.new(text) 99 | expect(wrapping.wrap(45)).to eq unindent <<-EOS 100 | What of it, if some old hunks of a \nsea-captain orders me to get a broom and \n sweep down the decks? What does that \nindignity amount to, weighed, I mean, in the \n scales of the New Testament? Do you think \nthe archangel Gabriel thinks anything the \nless of me, because I promptly and \nrespectfully obey that old hunks in that \nparticular instance? Who ain't a slave? Tell \nme that. Well, then, however the old \nsea-captains may order me about--however \nthey may thump and punch me about, I have \nthe satisfaction of knowing that it is all \nright; 101 | EOS 102 | end 103 | end 104 | 105 | context 'with newlines' do 106 | it "preserves newlines for both prefix and postfix" do 107 | text = "\n\nラドクリフ、マラソン五輪代表に1万m出場にも含み\n\n\n" 108 | wrapping = Verse::Wrapping.new(text) 109 | expect(wrapping.wrap(10)).to eq([ 110 | "\n\nラドクリフ", 111 | "、マラソン", 112 | "五輪代表に", 113 | "1万m出場に", 114 | "も含み\n\n\n" 115 | ].join("\n")) 116 | end 117 | end 118 | 119 | context 'with ANSI codes' do 120 | it "wraps ANSI chars" do 121 | text = "\e[32;44mIgnorance is the parent of fear.\e[0m" 122 | wrapping = Verse::Wrapping.new(text) 123 | expect(wrapping.wrap(14)).to eq([ 124 | "\e[32;44mIgnorance is \e[0m", 125 | "\e[32;44mthe parent of \e[0m", 126 | "\e[32;44mfear.\e[0m", 127 | ].join("\n")) 128 | end 129 | 130 | it "wraps ANSI in the middle of text" do 131 | text = "Ignorance is the \e[32mparent\e[0m of fear." 132 | wrapping = Verse::Wrapping.new(text) 133 | expect(wrapping.wrap(14)).to eq([ 134 | "Ignorance is ", 135 | "the \e[32mparent\e[0m of ", 136 | "fear.", 137 | ].join("\n")) 138 | end 139 | 140 | it "wraps multline ANSI codes" do 141 | text = "\e32;44mMulti\nLine\nContent.\e[0m" 142 | wrapping = Verse::Wrapping.new(text) 143 | expect(wrapping.wrap(14)).to eq([ 144 | "\e32;44mMulti\e[0m", 145 | "\e32;44mLine\e[0m", 146 | "\e32;44mContent.\e[0m", 147 | ].join("\n")) 148 | end 149 | 150 | it "wraps multiple ANSI codes in a single line" do 151 | text = "Talk \e[32mnot\e[0m to me of \e[33mblasphemy\e[0m, man; I'd \e[35mstrike the sun\e[0m if it insulted me." 152 | wrapping = Verse::Wrapping.new(text) 153 | expect(wrapping.wrap(30)).to eq([ 154 | "Talk \e[32mnot\e[0m to me of \e[33mblasphemy\e[0m, ", 155 | "man; I'd \e[35mstrike the sun\e[0m if it ", 156 | "insulted me." 157 | ].join("\n")) 158 | end 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /tasks/console.rake: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | desc 'Load gem inside irb console' 4 | task :console do 5 | require 'irb' 6 | require 'irb/completion' 7 | require File.join(__FILE__, '../../lib/verse') 8 | ARGV.clear 9 | IRB.start 10 | end 11 | -------------------------------------------------------------------------------- /tasks/coverage.rake: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | desc 'Measure code coverage' 4 | task :coverage do 5 | begin 6 | original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true' 7 | Rake::Task['spec'].invoke 8 | ensure 9 | ENV['COVERAGE'] = original 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /tasks/spec.rake: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | begin 4 | require 'rspec/core/rake_task' 5 | 6 | desc 'Run all specs' 7 | RSpec::Core::RakeTask.new(:spec) do |task| 8 | task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb' 9 | end 10 | 11 | namespace :spec do 12 | desc 'Run unit specs' 13 | RSpec::Core::RakeTask.new(:unit) do |task| 14 | task.pattern = 'spec/unit{,/*/**}/*_spec.rb' 15 | end 16 | 17 | desc 'Run integration specs' 18 | RSpec::Core::RakeTask.new(:integration) do |task| 19 | task.pattern = 'spec/integration{,/*/**}/*_spec.rb' 20 | end 21 | end 22 | 23 | rescue LoadError 24 | %w[spec spec:unit spec:integration].each do |name| 25 | task name do 26 | $stderr.puts "In order to run #{name}, do `gem install rspec`" 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /verse.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'verse/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "verse" 8 | spec.version = Verse::VERSION 9 | spec.authors = ["Piotr Murach"] 10 | spec.email = [""] 11 | spec.summary = %q{Text transformations such as truncation, wrapping, aligning, indentation and grouping of words.} 12 | spec.description = %q{Text transformations such as truncation, wrapping, aligning, indentation and grouping of words.} 13 | spec.homepage = "https://github.com/piotrmurach/verse" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(spec)/}) 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_dependency 'unicode_utils', '~> 1.4.0' 22 | spec.add_dependency 'unicode-display_width','~> 1.1.0' 23 | 24 | spec.add_development_dependency 'bundler', '~> 1.5' 25 | end 26 | --------------------------------------------------------------------------------