pastel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +22 -0
- data/README.md +191 -0
- data/Rakefile +8 -0
- data/lib/pastel.rb +20 -0
- data/lib/pastel/ansi.rb +54 -0
- data/lib/pastel/color.rb +139 -0
- data/lib/pastel/color_resolver.rb +23 -0
- data/lib/pastel/decorator_chain.rb +40 -0
- data/lib/pastel/delegator.rb +44 -0
- data/lib/pastel/version.rb +5 -0
- data/pastel.gemspec +23 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/unit/color/code_spec.rb +21 -0
- data/spec/unit/color/decorate_spec.rb +41 -0
- data/spec/unit/color/strip_spec.rb +47 -0
- data/spec/unit/color/styles_spec.rb +12 -0
- data/spec/unit/decorator_chain_spec.rb +49 -0
- data/spec/unit/delegator_spec.rb +23 -0
- data/spec/unit/new_spec.rb +61 -0
- data/tasks/console.rake +10 -0
- data/tasks/coverage.rake +11 -0
- data/tasks/spec.rake +29 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: df0d822f2378510e2c8f751d179c208585fb2e38
|
4
|
+
data.tar.gz: bf0a8a93193af9c35ff862e0e69373d97bf29d51
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7dcf28176858eeaccc03eb33d702214274e72d32082887d66938afa1f7d6f38b24fa7523437ed4249d51e6c7425db729e42ad50cd83daf0747ea953abb96157c
|
7
|
+
data.tar.gz: c6a17854cb0c7328474da74d4c8ffee8f0af6c757f729bbef91ec02da7202e91d4da108ef318be8dcf4ab51e1fa20a9985cd8e3ed35c4f5f50db3de7e888488d
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pastel
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/.travis.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
language: ruby
|
2
|
+
bundler_args: --without yard benchmarks
|
3
|
+
script: "bundle exec rake ci"
|
4
|
+
rvm:
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
- 2.1.0
|
8
|
+
- ruby-head
|
9
|
+
matrix:
|
10
|
+
include:
|
11
|
+
- rvm: jruby-19mode
|
12
|
+
- rvm: jruby-20mode
|
13
|
+
- rvm: jruby-21mode
|
14
|
+
- rvm: jruby-head
|
15
|
+
- rvm: rbx-2
|
16
|
+
allow_failures:
|
17
|
+
- rvm: ruby-head
|
18
|
+
- rvm: jruby-head
|
19
|
+
- rvm: jruby-20mode
|
20
|
+
- rvm: jruby-21mode
|
21
|
+
- rvm: rbx-2
|
22
|
+
fast_finish: true
|
23
|
+
branches:
|
24
|
+
only: master
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rake', '~> 10.3.2'
|
7
|
+
gem 'rspec', '~> 3.1.0'
|
8
|
+
gem 'yard', '~> 0.8.7'
|
9
|
+
end
|
10
|
+
|
11
|
+
group :metrics do
|
12
|
+
gem 'coveralls', '~> 0.7.0'
|
13
|
+
gem 'simplecov', '~> 0.8.2'
|
14
|
+
gem 'yardstick', '~> 0.9.9'
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 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.
|
data/README.md
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
# Pastel
|
2
|
+
[][gem]
|
3
|
+
[][travis]
|
4
|
+
[][codeclimate]
|
5
|
+
|
6
|
+
[gem]: http://badge.fury.io/rb/pastel
|
7
|
+
[travis]: http://travis-ci.org/peter-murach/pastel
|
8
|
+
[codeclimate]: https://codeclimate.com/github/peter-murach/pastel
|
9
|
+
|
10
|
+
Terminal output styling with intuitive and clean API that doesn't monkey patch String class.
|
11
|
+
|
12
|
+
**Pastel** is minimal and focused to work in all terminal emulators.
|
13
|
+
|
14
|
+
## Features
|
15
|
+
|
16
|
+
* Doesn't monkey patch `String`
|
17
|
+
* Intuitive and expressive API
|
18
|
+
* Minimal and focused to work on all terminal emulators
|
19
|
+
* Auto-detection of color support
|
20
|
+
* Performant
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
gem 'pastel'
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install pastel
|
35
|
+
|
36
|
+
## Contents
|
37
|
+
|
38
|
+
* [1. Usage](#1-usage)
|
39
|
+
* [2. Interface](#2-interface)
|
40
|
+
* [2.1 Color](#21-color)
|
41
|
+
* [2.2 Decorate](#22-decorate)
|
42
|
+
* [2.3 Strip](#23-strip)
|
43
|
+
* [2.4 Styles](#24-styles)
|
44
|
+
* [2.5 Valid?](#25-valid)
|
45
|
+
* [3. The available styles](#3-the-available-styles)
|
46
|
+
|
47
|
+
## 1 Usage
|
48
|
+
|
49
|
+
**Pastel** provides a simple, minimal and intuitive API for styling your strings:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
pastel = Pastel.new
|
53
|
+
|
54
|
+
pastel.red('Unicorns!')
|
55
|
+
```
|
56
|
+
|
57
|
+
It allows you to combine styled strings with regular ones:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
pastel.red('Unicorns') + ' will rule ' + pastel.green('the World!')
|
61
|
+
```
|
62
|
+
|
63
|
+
You can compose multiple styles through chainable API:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
pastel.red.on_green.bold('Unicorns!')
|
67
|
+
```
|
68
|
+
|
69
|
+
It supports variable number of arguments with individual styling:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
pastel.red('Unicorns', 'are', 'running', 'everywhere!')
|
73
|
+
```
|
74
|
+
|
75
|
+
You can also nest styles as follows:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
pastel.red('Unicorns ', pastel.on_green('everywhere!'))
|
79
|
+
```
|
80
|
+
|
81
|
+
## 2 Interface
|
82
|
+
|
83
|
+
### 2.1 Color
|
84
|
+
|
85
|
+
You can pass variable number of styled strings like so:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
pastel.red('Unicorns', pastel.on_yellow('are running', pastel.bold.underline('everywhere')), '!')
|
89
|
+
```
|
90
|
+
|
91
|
+
Please refer to [3. The available styles](#3-the-available-styles) section for full list of supported styles.
|
92
|
+
|
93
|
+
### 2.2 Decorate
|
94
|
+
|
95
|
+
This method is a lower level string styling call that takes as the first argument the string to style and any number of attributes, and returns string wrapped in styles.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
pastel.decorate('Unicorn', :green, :on_blue, :bold)
|
99
|
+
```
|
100
|
+
|
101
|
+
### 2.3 Strip
|
102
|
+
|
103
|
+
Strip all color sequence characters:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
pastel.strip("\e[1m\e[34mbold blue text\e[0m"") # => "bold blue text"
|
107
|
+
```
|
108
|
+
|
109
|
+
### 2.4 Styles
|
110
|
+
|
111
|
+
To get a full list of supported styles with the corresponding color codes do:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
pastel.styles
|
115
|
+
```
|
116
|
+
|
117
|
+
### 2.5 Valid?
|
118
|
+
|
119
|
+
Determine whether a color is valid:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
pastel.valid?(:red) # => true
|
123
|
+
pastel.valid?(:unicorn) # => false
|
124
|
+
```
|
125
|
+
|
126
|
+
## 3 The available styles
|
127
|
+
|
128
|
+
**Pastel** works with terminal emulators that support minimum sixteen colors. It provides `16` basic colors and `8` styles with further `16` bright color pairs. The corresponding bright color is obtained by prepending the `bright` to the normal color name. For example, color `red` will have `bright_red` as its pair.
|
129
|
+
|
130
|
+
The variant with `on_` prefix will style the text background color.
|
131
|
+
|
132
|
+
The foreground colors:
|
133
|
+
|
134
|
+
* `black`
|
135
|
+
* `red`
|
136
|
+
* `green`
|
137
|
+
* `yellow`
|
138
|
+
* `blue`
|
139
|
+
* `magenta`
|
140
|
+
* `cyan`
|
141
|
+
* `white`
|
142
|
+
* `bright_black`
|
143
|
+
* `bright_red`
|
144
|
+
* `bright_green`
|
145
|
+
* `bright_yellow`
|
146
|
+
* `bright_blue`
|
147
|
+
* `bright_magenta`
|
148
|
+
* `bright_cyan`
|
149
|
+
* `bright_white`
|
150
|
+
|
151
|
+
The background colors:
|
152
|
+
|
153
|
+
* `on_black`
|
154
|
+
* `on_red`
|
155
|
+
* `on_green`
|
156
|
+
* `on_yellow`
|
157
|
+
* `on_blue`
|
158
|
+
* `on_magenta`
|
159
|
+
* `on_cyan`
|
160
|
+
* `on_white`
|
161
|
+
* `on_bright_black`
|
162
|
+
* `on_bright_red`
|
163
|
+
* `on_bright_green`
|
164
|
+
* `on_bright_yellow`
|
165
|
+
* `on_bright_blue`
|
166
|
+
* `on_bright_magenta`
|
167
|
+
* `on_bright_cyan`
|
168
|
+
* `on_bright_white`
|
169
|
+
|
170
|
+
Generic styles:
|
171
|
+
|
172
|
+
* `clear`
|
173
|
+
* `bold`
|
174
|
+
* `dim`
|
175
|
+
* `italic`
|
176
|
+
* `underline`
|
177
|
+
* `inverse`
|
178
|
+
* `hidden`
|
179
|
+
* `strikethrough`
|
180
|
+
|
181
|
+
## Contributing
|
182
|
+
|
183
|
+
1. Fork it ( https://github.com/[my-github-username]/pastel/fork )
|
184
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
185
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
186
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
187
|
+
5. Create a new Pull Request
|
188
|
+
|
189
|
+
## Copyright
|
190
|
+
|
191
|
+
Copyright (c) 2014 Piotr Murach. See LICENSE for further details.
|
data/Rakefile
ADDED
data/lib/pastel.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'equatable'
|
4
|
+
require 'pastel/ansi'
|
5
|
+
require 'pastel/color'
|
6
|
+
require 'pastel/color_resolver'
|
7
|
+
require 'pastel/delegator'
|
8
|
+
require 'pastel/decorator_chain'
|
9
|
+
require 'pastel/version'
|
10
|
+
|
11
|
+
module Pastel
|
12
|
+
# Raised when the style attribute is not supported
|
13
|
+
InvalidAttributeNameError = Class.new(::ArgumentError)
|
14
|
+
|
15
|
+
def new
|
16
|
+
Delegator.for(DecoratorChain.empty)
|
17
|
+
end
|
18
|
+
|
19
|
+
module_function :new
|
20
|
+
end # Pastel
|
data/lib/pastel/ansi.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Pastel
|
4
|
+
# Mixin that provides ANSI codes
|
5
|
+
module ANSI
|
6
|
+
CLEAR = "\e[0m"
|
7
|
+
|
8
|
+
BOLD = "\e[1m"
|
9
|
+
DIM = "\e[2m"
|
10
|
+
ITALIC = "\e[3m"
|
11
|
+
UNDERLINE = "\e[4m"
|
12
|
+
INVERSE = "\e[7m"
|
13
|
+
HIDDEN = "\e[8m"
|
14
|
+
STRIKETHROUGH = "\e[9m"
|
15
|
+
|
16
|
+
# Escape codes for text color.
|
17
|
+
BLACK = "\e[30m"
|
18
|
+
RED = "\e[31m"
|
19
|
+
GREEN = "\e[32m"
|
20
|
+
YELLOW = "\e[33m"
|
21
|
+
BLUE = "\e[34m"
|
22
|
+
MAGENTA = "\e[35m"
|
23
|
+
CYAN = "\e[36m"
|
24
|
+
WHITE = "\e[37m"
|
25
|
+
|
26
|
+
BRIGHT_BLACK = "\e[90m"
|
27
|
+
BRIGHT_RED = "\e[91m"
|
28
|
+
BRIGHT_GREEN = "\e[92m"
|
29
|
+
BRIGHT_YELLOW = "\e[93m"
|
30
|
+
BRIGHT_BLUE = "\e[94m"
|
31
|
+
BRIGHT_MAGENTA = "\e[95m"
|
32
|
+
BRIGHT_CYAN = "\e[96m"
|
33
|
+
|
34
|
+
# Escape codes for background color.
|
35
|
+
ON_BLACK = "\e[40m"
|
36
|
+
ON_RED = "\e[41m"
|
37
|
+
ON_GREEN = "\e[42m"
|
38
|
+
ON_YELLOW = "\e[43m"
|
39
|
+
ON_BLUE = "\e[44m"
|
40
|
+
ON_MAGENTA = "\e[45m"
|
41
|
+
ON_CYAN = "\e[46m"
|
42
|
+
ON_WHITE = "\e[47m"
|
43
|
+
|
44
|
+
ON_BRIGHT_BLACK = "\e[100m"
|
45
|
+
ON_BRIGHT_RED = "\e[101m"
|
46
|
+
ON_BRIGHT_GREEN = "\e[102m"
|
47
|
+
ON_BRIGHT_YELLOW = "\e[103m"
|
48
|
+
ON_BRIGHT_BLUE = "\e[104m"
|
49
|
+
ON_BRIGHT_MAGENTA = "\e[105m"
|
50
|
+
ON_BRIGHT_CYAN = "\e[106m"
|
51
|
+
|
52
|
+
BACKGROUND_COLORS = constants.grep(/^ON_*/).freeze
|
53
|
+
end # ANSI
|
54
|
+
end # Pastel
|
data/lib/pastel/color.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Pastel
|
4
|
+
# A class responsible for coloring strings.
|
5
|
+
class Color
|
6
|
+
include Equatable
|
7
|
+
include ANSI
|
8
|
+
|
9
|
+
attr_reader :enabled
|
10
|
+
|
11
|
+
# Initialize a Terminal Color
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def initialize(enabled = false)
|
15
|
+
@enabled = enabled
|
16
|
+
end
|
17
|
+
|
18
|
+
# Disable coloring of this terminal session
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
def disable!
|
22
|
+
@enabled = false
|
23
|
+
end
|
24
|
+
|
25
|
+
# Apply ANSI color to the given string.
|
26
|
+
#
|
27
|
+
# @param [String] string
|
28
|
+
# text to add ANSI strings
|
29
|
+
#
|
30
|
+
# @param [Array[Symbol]] colors
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# color.decorate "text", :yellow, :on_green, :underline
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def decorate(string, *colors)
|
39
|
+
return string if string.empty?
|
40
|
+
validate(*colors)
|
41
|
+
ansi_colors = colors.map { |color| lookup(color) }
|
42
|
+
ansi_string = "#{ansi_colors.join}#{string}#{ANSI::CLEAR}"
|
43
|
+
if ansi_string =~ /(#{Regexp.quote(ANSI::CLEAR)}){2,}/
|
44
|
+
ansi_string.gsub!(/(#{Regexp.quote(ANSI::CLEAR)}){2,}/, '')
|
45
|
+
end
|
46
|
+
matches = ansi_string.scan(/#{Regexp.quote(ANSI::CLEAR)}/)
|
47
|
+
if matches.length >= 2
|
48
|
+
ansi_string.sub!(/#{Regexp.quote(ANSI::CLEAR)}/, ansi_colors.join)
|
49
|
+
end
|
50
|
+
ansi_string
|
51
|
+
end
|
52
|
+
|
53
|
+
# Same as instance method.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def self.decorate(string, *colors)
|
59
|
+
new.decorate(string, *colors)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Strip ANSI color codes from a string.
|
63
|
+
#
|
64
|
+
# @param [String] string
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def strip(string)
|
70
|
+
string.to_s.gsub(/(\[)?\033(\[)?[;?\d]*[\dA-Za-z](\])?/, '')
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return raw color code without embeding it into a string.
|
74
|
+
#
|
75
|
+
# @return [Array[String]]
|
76
|
+
# ANSI escape codes
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def code(*colors)
|
80
|
+
validate(*colors)
|
81
|
+
colors.map { |color| lookup(color) }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Find color representation.
|
85
|
+
#
|
86
|
+
# @param [Symbol,String] color
|
87
|
+
# the color name to lookup by
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
# the ANSI code
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
def lookup(color)
|
94
|
+
self.class.const_get(color.to_s.upcase)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Expose all ANSI color names and their codes
|
98
|
+
#
|
99
|
+
# @return [Hash[Symbol]]
|
100
|
+
#
|
101
|
+
# @api public
|
102
|
+
def styles
|
103
|
+
ANSI.constants(false).each_with_object({}) do |col, acc|
|
104
|
+
acc[col.to_sym.downcase] = lookup(col)
|
105
|
+
acc
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# List all available style names
|
110
|
+
#
|
111
|
+
# @return [Array[Symbol]]
|
112
|
+
#
|
113
|
+
# @api public
|
114
|
+
def style_names
|
115
|
+
styles.keys
|
116
|
+
end
|
117
|
+
|
118
|
+
# Check if provided colors are known colors
|
119
|
+
#
|
120
|
+
# @param [Array[Symbol,String]]
|
121
|
+
# the colors to check
|
122
|
+
#
|
123
|
+
# @reutrn [Boolean]
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def valid?(*colors)
|
127
|
+
colors.all? { |color| style_names.include?(color.to_sym) }
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
# @api private
|
133
|
+
def validate(*colors)
|
134
|
+
return if valid?(*colors)
|
135
|
+
fail InvalidAttributeNameError, 'Bad style or unintialized constant, ' \
|
136
|
+
" valid styles are: #{style_names.join(', ')}."
|
137
|
+
end
|
138
|
+
end # Color
|
139
|
+
end # TTY
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Pastel
|
4
|
+
# Contains logic for resolving styles applied to component
|
5
|
+
#
|
6
|
+
# Used internally by {Delegator}.
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class ColorResolver
|
10
|
+
attr_reader :color
|
11
|
+
|
12
|
+
def initialize(color = Color.new)
|
13
|
+
@color = color
|
14
|
+
end
|
15
|
+
|
16
|
+
def resolve(base, *args)
|
17
|
+
unprocessed_string = args.join
|
18
|
+
base.reduce(unprocessed_string) do |component, decorator|
|
19
|
+
color.decorate(component, decorator)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end # ColorResolver
|
23
|
+
end # Pastel
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Pastel
|
4
|
+
# Collects a list of decorators for styling a string
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class DecoratorChain
|
8
|
+
include Enumerable
|
9
|
+
include Equatable
|
10
|
+
|
11
|
+
attr_reader :decorators
|
12
|
+
|
13
|
+
def initialize(decorators = [])
|
14
|
+
@decorators = decorators
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add decorator
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def add(decorator)
|
21
|
+
self.class.new(decorators + [decorator])
|
22
|
+
end
|
23
|
+
|
24
|
+
# Iterate over list of decorators
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def each(&block)
|
28
|
+
decorators.each(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create an empty decorator chain
|
32
|
+
#
|
33
|
+
# @return [DecoratorChain]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def self.empty
|
37
|
+
new([])
|
38
|
+
end
|
39
|
+
end # DecoratorChain
|
40
|
+
end # Patel
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Pastel
|
4
|
+
# Wrapes the {DecoratorChain} to allow for easy resolution
|
5
|
+
# of string coloring.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Delegator
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
attr_reader :base
|
12
|
+
|
13
|
+
attr_reader :resolver
|
14
|
+
|
15
|
+
def_delegators :@color, :valid?, :styles, :strip, :decorate
|
16
|
+
|
17
|
+
def initialize(base)
|
18
|
+
@base = base
|
19
|
+
@color = Color.new
|
20
|
+
@resolver = ColorResolver.new(@color)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api public
|
24
|
+
def self.for(value)
|
25
|
+
new(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def method_missing(method_name, *args, &block)
|
31
|
+
new_base = base.add(method_name)
|
32
|
+
delegator = self.class.new(new_base)
|
33
|
+
if args.empty?
|
34
|
+
delegator
|
35
|
+
else
|
36
|
+
resolver.resolve(new_base, *args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to_missing?(name, include_all = false)
|
41
|
+
super || @color.respond_to?(name, include_all)
|
42
|
+
end
|
43
|
+
end # Delegator
|
44
|
+
end # Pastel
|
data/pastel.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pastel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pastel"
|
8
|
+
spec.version = Pastel::VERSION
|
9
|
+
spec.authors = ["Piotr Murach"]
|
10
|
+
spec.email = [""]
|
11
|
+
spec.summary = %q{Terminal strings styling with intuitive and clean API.}
|
12
|
+
spec.description = %q{Terminal strings styling with intuitive and clean API.}
|
13
|
+
spec.homepage = "https://github.com/peter-murach/pastel"
|
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{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "equatable", "~> 0.5"
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'pastel'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.expect_with :rspec do |expectations|
|
7
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.mock_with :rspec do |mocks|
|
11
|
+
mocks.verify_partial_doubles = true
|
12
|
+
end
|
13
|
+
|
14
|
+
# Limits the available syntax to the non-monkey patched syntax that is recommended.
|
15
|
+
config.disable_monkey_patching!
|
16
|
+
|
17
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
18
|
+
# be too noisy due to issues in dependencies.
|
19
|
+
config.warnings = true
|
20
|
+
|
21
|
+
if config.files_to_run.one?
|
22
|
+
config.default_formatter = 'doc'
|
23
|
+
end
|
24
|
+
|
25
|
+
config.profile_examples = 2
|
26
|
+
|
27
|
+
config.order = :random
|
28
|
+
|
29
|
+
Kernel.srand config.seed
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pastel::Color, '.code' do
|
6
|
+
let(:string) { "This is a \e[1m\e[34mbold blue text\e[0m" }
|
7
|
+
|
8
|
+
subject(:color) { described_class.new }
|
9
|
+
|
10
|
+
it 'finds single code' do
|
11
|
+
expect(color.code(:black)).to eq(["\e[30m"])
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'finds more than one code' do
|
15
|
+
expect(color.code(:black, :green)).to eq(["\e[30m", "\e[32m"])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't find code" do
|
19
|
+
expect { color.code(:unkown) }.to raise_error(ArgumentError)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pastel::Color, '.decorate' do
|
6
|
+
let(:string) { 'string' }
|
7
|
+
|
8
|
+
subject(:color) { described_class.new }
|
9
|
+
|
10
|
+
it "doesn't apply styling to empty string" do
|
11
|
+
expect(color.decorate('')).to eq('')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'applies green text to string' do
|
15
|
+
expect(color.decorate(string, :green)).to eq("\e[32m#{string}\e[0m")
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'applies red text background to string' do
|
19
|
+
expect(color.decorate(string, :on_red)).to eq("\e[41m#{string}\e[0m")
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'applies style and color to string' do
|
23
|
+
expect(color.decorate(string, :bold, :green)).to eq("\e[1m\e[32m#{string}\e[0m")
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'applies style, color and background to string' do
|
27
|
+
text = color.decorate(string, :bold, :green, :on_blue)
|
28
|
+
expect(text).to eq("\e[1m\e[32m\e[44m#{string}\e[0m")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "applies styles to nested text" do
|
32
|
+
decorated = color.decorate(string + color.decorate(string, :red) + string, :green)
|
33
|
+
expect(decorated).to eq("\e[32m#{string}\e[31m#{string}\e[32m#{string}\e[0m")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'errors for unknown color' do
|
37
|
+
expect {
|
38
|
+
color.decorate(string, :crimson)
|
39
|
+
}.to raise_error(Pastel::InvalidAttributeNameError)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pastel::Color, '.strip' do
|
6
|
+
let(:instance) { described_class.new }
|
7
|
+
|
8
|
+
subject(:color) { instance.strip(string) }
|
9
|
+
|
10
|
+
context 'with ansi colors' do
|
11
|
+
let(:string) { "This is a \e[1m\e[34mbold blue text\e[0m" }
|
12
|
+
|
13
|
+
it 'removes color from string' do
|
14
|
+
is_expected.to eq('This is a bold blue text')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'with octal in encapsulating brackets' do
|
19
|
+
let(:string) { "\[\033[01;32m\]u@h \[\033[01;34m\]W $ \[\033[00m\]" }
|
20
|
+
|
21
|
+
it { is_expected.to eq('u@h W $ ') }
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with octal without brackets' do
|
25
|
+
let(:string) { "\033[01;32mu@h \033[01;34mW $ \033[00m" }
|
26
|
+
|
27
|
+
it { is_expected.to eq('u@h W $ ') }
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with octal with multiple colors' do
|
31
|
+
let(:string) { "\e[3;0;0;t\e[8;50;0t" }
|
32
|
+
|
33
|
+
it { is_expected.to eq('') }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with control codes' do
|
37
|
+
let(:string ) { "WARN. \x1b[1m&\x1b[0m ERR. \x1b[7m&\x1b[0m" }
|
38
|
+
|
39
|
+
it { is_expected.to eq('WARN. & ERR. &') }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with escape byte' do
|
43
|
+
let(:string) { "This is a \e[1m\e[34mbold blue text\e[0m" }
|
44
|
+
|
45
|
+
it { is_expected.to eq("This is a bold blue text") }
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pastel::DecoratorChain do
|
6
|
+
it "is enumerable" do
|
7
|
+
expect(described_class.new).to be_a(Enumerable)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is equatable" do
|
11
|
+
expect(described_class.new).to be_a(Equatable)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".each" do
|
15
|
+
it "yields each decorator" do
|
16
|
+
first = double('first')
|
17
|
+
second = double('second')
|
18
|
+
chain = described_class.new.add(first).add(second)
|
19
|
+
yielded = []
|
20
|
+
|
21
|
+
expect {
|
22
|
+
chain.each { |decorator| yielded << decorator }
|
23
|
+
}.to change { yielded }.from([]).to([first, second])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe ".==" do
|
28
|
+
it "is equivalent with the same decorator" do
|
29
|
+
expect(described_class.new.add(:foo).add(:bar)).
|
30
|
+
to eq(described_class.new.add(:foo).add(:bar))
|
31
|
+
end
|
32
|
+
|
33
|
+
it "is not equivalent with different decorator" do
|
34
|
+
expect(described_class.new.add(:foo).add(:bar)).
|
35
|
+
not_to eq(described_class.new.add(:foo).add(:baz))
|
36
|
+
end
|
37
|
+
|
38
|
+
it "is not equivalent to another type" do
|
39
|
+
expect(described_class.new.add(:foo).add(:bar)).
|
40
|
+
not_to eq(:other)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe ".inspect" do
|
45
|
+
it "displays object information" do
|
46
|
+
expect(described_class.new.inspect).to match(/decorators=\[\]/)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pastel::Delegator do
|
6
|
+
describe ".respond_to_missing?" do
|
7
|
+
context 'for a method defined on' do
|
8
|
+
it "returns true" do
|
9
|
+
chain = double(:chain)
|
10
|
+
decorator = described_class.new(chain)
|
11
|
+
expect(decorator.method(:styles)).not_to be_nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "for an undefined method " do
|
16
|
+
it "returns false" do
|
17
|
+
chain = double(:chain)
|
18
|
+
decorator = described_class.new(chain)
|
19
|
+
expect { decorator.method(:unknown) }.to raise_error(NameError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Pastel do
|
6
|
+
|
7
|
+
subject(:pastel) { described_class.new }
|
8
|
+
|
9
|
+
describe 'coloring string' do
|
10
|
+
it "doesn't apply styles to empty string" do
|
11
|
+
expect(pastel.red('')).to eq('')
|
12
|
+
end
|
13
|
+
|
14
|
+
it "colors string" do
|
15
|
+
expect(pastel.red("unicorn")).to eq("\e[31municorn\e[0m")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "allows to specify variable number of arguments" do
|
19
|
+
expect(pastel.red("unicorn", "running")).to eq("\e[31municornrunning\e[0m")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "combines colored strings with regular ones" do
|
23
|
+
expect(pastel.red("Unicorns") + ' will rule ' + pastel.green('the World!')).
|
24
|
+
to eq("\e[31mUnicorns\e[0m will rule \e[32mthe World!\e[0m")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "composes color strings" do
|
28
|
+
expect(pastel.red.on_green.underline("unicorn")).
|
29
|
+
to eq("\e[4m\e[42m\e[31municorn\e[0m")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "allows to nest mixed styles" do
|
33
|
+
expect(pastel.red("Unicorn" + pastel.green.on_yellow.underline('running') + '!')).
|
34
|
+
to eq("\e[31mUnicorn\e[4m\e[43m\e[32mrunning\e[31m!\e[0m")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "allows for deep nesting" do
|
38
|
+
expect(pastel.red('r' + pastel.green('g' + pastel.yellow('y') + 'g') + 'r')).
|
39
|
+
to eq("\e[31mr\e[32mg\e[33my\e[32mg\e[31mr\e[0m")
|
40
|
+
end
|
41
|
+
|
42
|
+
it "allows for variable nested arguments" do
|
43
|
+
expect(pastel.red('r', pastel.green('g'), 'r')).
|
44
|
+
to eq("\e[31mr\e[32mg\e[31mr\e[0m")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.valid?' do
|
49
|
+
it "when valid returns true" do
|
50
|
+
expect(pastel.valid?(:red)).to eq(true)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns false when invalid" do
|
54
|
+
expect(pastel.valid?(:unknown)).to eq(false)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "respond to styles method" do
|
59
|
+
expect(pastel).to respond_to(:styles)
|
60
|
+
end
|
61
|
+
end
|
data/tasks/console.rake
ADDED
data/tasks/coverage.rake
ADDED
data/tasks/spec.rake
ADDED
@@ -0,0 +1,29 @@
|
|
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
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pastel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Piotr Murach
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: equatable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.5'
|
41
|
+
description: Terminal strings styling with intuitive and clean API.
|
42
|
+
email:
|
43
|
+
- ''
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- .rspec
|
50
|
+
- .ruby-gemset
|
51
|
+
- .ruby-version
|
52
|
+
- .travis.yml
|
53
|
+
- Gemfile
|
54
|
+
- LICENSE.txt
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- lib/pastel.rb
|
58
|
+
- lib/pastel/ansi.rb
|
59
|
+
- lib/pastel/color.rb
|
60
|
+
- lib/pastel/color_resolver.rb
|
61
|
+
- lib/pastel/decorator_chain.rb
|
62
|
+
- lib/pastel/delegator.rb
|
63
|
+
- lib/pastel/version.rb
|
64
|
+
- pastel.gemspec
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
- spec/unit/color/code_spec.rb
|
67
|
+
- spec/unit/color/decorate_spec.rb
|
68
|
+
- spec/unit/color/strip_spec.rb
|
69
|
+
- spec/unit/color/styles_spec.rb
|
70
|
+
- spec/unit/decorator_chain_spec.rb
|
71
|
+
- spec/unit/delegator_spec.rb
|
72
|
+
- spec/unit/new_spec.rb
|
73
|
+
- tasks/console.rake
|
74
|
+
- tasks/coverage.rake
|
75
|
+
- tasks/spec.rake
|
76
|
+
homepage: https://github.com/peter-murach/pastel
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.0.3
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Terminal strings styling with intuitive and clean API.
|
100
|
+
test_files:
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/unit/color/code_spec.rb
|
103
|
+
- spec/unit/color/decorate_spec.rb
|
104
|
+
- spec/unit/color/strip_spec.rb
|
105
|
+
- spec/unit/color/styles_spec.rb
|
106
|
+
- spec/unit/decorator_chain_spec.rb
|
107
|
+
- spec/unit/delegator_spec.rb
|
108
|
+
- spec/unit/new_spec.rb
|
109
|
+
has_rdoc:
|