planter-cli 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +9 -0
- data/.gitignore +44 -0
- data/.irbrc +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +78 -0
- data/.travis.yml +7 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +6 -0
- data/Guardfile +25 -0
- data/LICENSE.txt +20 -0
- data/README.md +208 -0
- data/Rakefile +132 -0
- data/bin/plant +106 -0
- data/debug.log +0 -0
- data/docker/Dockerfile +12 -0
- data/docker/Dockerfile-2.6 +12 -0
- data/docker/Dockerfile-2.7 +12 -0
- data/docker/Dockerfile-3.0 +11 -0
- data/docker/bash_profile +15 -0
- data/docker/inputrc +57 -0
- data/lib/.rubocop.yml +1 -0
- data/lib/planter/array.rb +28 -0
- data/lib/planter/color.rb +370 -0
- data/lib/planter/errors.rb +59 -0
- data/lib/planter/file.rb +11 -0
- data/lib/planter/fileentry.rb +87 -0
- data/lib/planter/filelist.rb +144 -0
- data/lib/planter/hash.rb +103 -0
- data/lib/planter/plant.rb +228 -0
- data/lib/planter/prompt.rb +352 -0
- data/lib/planter/script.rb +59 -0
- data/lib/planter/string.rb +383 -0
- data/lib/planter/symbol.rb +28 -0
- data/lib/planter/version.rb +7 -0
- data/lib/planter.rb +222 -0
- data/planter-cli.gemspec +48 -0
- data/scripts/deploy.rb +97 -0
- data/scripts/runtests.sh +5 -0
- data/spec/.rubocop.yml +4 -0
- data/spec/planter/plant_spec.rb +14 -0
- data/spec/planter/string_spec.rb +20 -0
- data/spec/spec_helper.rb +20 -0
- data/src/_README.md +214 -0
- metadata +400 -0
data/bin/plant
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$VERBOSE = true
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require_relative '../lib/planter'
|
8
|
+
|
9
|
+
options = {
|
10
|
+
help: false,
|
11
|
+
debug: false,
|
12
|
+
version: false
|
13
|
+
}
|
14
|
+
|
15
|
+
# Variable definition
|
16
|
+
# variables:
|
17
|
+
# - key: var
|
18
|
+
# prompt: Variable
|
19
|
+
# type: [string,float,integer,number,date]
|
20
|
+
# value: (for date type can be today, time, now, etc.)
|
21
|
+
# default: Untitled
|
22
|
+
# min: 1
|
23
|
+
# max: 5
|
24
|
+
Planter.variables = {}
|
25
|
+
Planter::Color.coloring = $stdout.isatty
|
26
|
+
|
27
|
+
opts = OptionParser.new
|
28
|
+
opts.banner = 'Usage: planter [options] TEMPLATE'
|
29
|
+
|
30
|
+
Planter.accept_defaults = false
|
31
|
+
opts.on('--defaults', 'Accept default values for all variables') do
|
32
|
+
Planter.accept_defaults = true
|
33
|
+
end
|
34
|
+
|
35
|
+
Planter.target = Dir.pwd
|
36
|
+
opts.on('-i', '--in TARGET', 'Plant in TARGET instead of current directory') do |opt|
|
37
|
+
target = File.expand_path(opt)
|
38
|
+
FileUtils.mkdir_p(target) unless File.exist?(target)
|
39
|
+
Planter.target = target
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-k', '--var=KEY:VALUE,KEY:VALUE...', Array,
|
43
|
+
'Pass a variable on the command line as KEY:VALUE pairs. Can be used multiple times.') do |opt|
|
44
|
+
opt.each do |o|
|
45
|
+
parts = o.split(/:/)
|
46
|
+
key = parts.shift
|
47
|
+
value = parts.join(':')
|
48
|
+
Planter.variables[key.to_var] = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on('-o', '--overwrite', 'Overwrite existing files') do
|
53
|
+
Planter.overwrite = true
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on_tail('-d', '--debug', 'Display version number') do
|
57
|
+
Planter.debug = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on_tail('-h', '--help', 'Display this screen, or list variables for template argument') do
|
61
|
+
options[:help] = true
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on_tail('-v', '--version', 'Display version number') do
|
65
|
+
options[:version] = true
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.parse!
|
69
|
+
|
70
|
+
##
|
71
|
+
## List variables for a template
|
72
|
+
##
|
73
|
+
## @param template [String] The template
|
74
|
+
##
|
75
|
+
def list_vars(template)
|
76
|
+
puts "#{template} variables:"
|
77
|
+
Planter.config = template
|
78
|
+
Planter.config[:variables].sort_by { |v| v[:key].to_var }.each do |var|
|
79
|
+
title = var[:prompt] || var[:key]
|
80
|
+
var_type = var[:type].normalize_type || :string
|
81
|
+
default = var[:value] || var[:default]
|
82
|
+
default = default ? ", default: #{default.coerce(var_type)}" : ''
|
83
|
+
puts "#{title}:"
|
84
|
+
puts " [#{var[:key].to_var}] (type: #{var_type}#{default})"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if options[:version]
|
89
|
+
puts "planter v#{Planter::VERSION}"
|
90
|
+
Process.exit 0
|
91
|
+
elsif options[:help]
|
92
|
+
if ARGV.count.zero?
|
93
|
+
puts opts
|
94
|
+
else
|
95
|
+
list_vars(ARGV[0])
|
96
|
+
end
|
97
|
+
Process.exit 0
|
98
|
+
elsif ARGV.count.zero?
|
99
|
+
raise Planter::Errors::ArgumentError.new 'Template argument required'
|
100
|
+
end
|
101
|
+
|
102
|
+
ARGV.each do |template|
|
103
|
+
Planter.config = template
|
104
|
+
app = Planter::Plant.new
|
105
|
+
app.plant
|
106
|
+
end
|
data/debug.log
ADDED
File without changes
|
data/docker/Dockerfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
FROM ruby:3.0.1
|
2
|
+
# RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
3
|
+
RUN mkdir /planter
|
4
|
+
WORKDIR /planter
|
5
|
+
|
6
|
+
RUN gem install bundler:2.2.29
|
7
|
+
RUN apt-get update -y
|
8
|
+
RUN apt-get install -y less vim
|
9
|
+
COPY ./docker/inputrc /root/.inputrc
|
10
|
+
COPY ./docker/bash_profile /root/.bash_profile
|
11
|
+
RUN mkdir -p /root/.config/planter/templates/test
|
12
|
+
CMD ["/planter/scripts/runtests.sh"]
|
@@ -0,0 +1,12 @@
|
|
1
|
+
FROM ruby:2.6
|
2
|
+
# RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
3
|
+
RUN mkdir /howzit
|
4
|
+
WORKDIR /howzit
|
5
|
+
# COPY ./ /howzit/
|
6
|
+
RUN gem install bundler:2.2.29
|
7
|
+
RUN apt-get update -y
|
8
|
+
RUN apt-get install -y less vim
|
9
|
+
COPY ./docker/inputrc /root/.inputrc
|
10
|
+
COPY ./docker/bash_profile /root/.bash_profile
|
11
|
+
RUN mkdir -p /root/.config/planter/templates/test
|
12
|
+
CMD ["/planter/scripts/runtests.sh"]
|
@@ -0,0 +1,12 @@
|
|
1
|
+
FROM ruby:2.7
|
2
|
+
# RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
3
|
+
RUN mkdir /howzit
|
4
|
+
WORKDIR /howzit
|
5
|
+
# COPY ./ /howzit/
|
6
|
+
RUN gem install bundler:2.2.29
|
7
|
+
RUN apt-get update -y
|
8
|
+
RUN apt-get install -y less vim
|
9
|
+
COPY ./docker/inputrc /root/.inputrc
|
10
|
+
COPY ./docker/bash_profile /root/.bash_profile
|
11
|
+
RUN mkdir -p /root/.config/planter/templates/test
|
12
|
+
CMD ["/planter/scripts/runtests.sh"]
|
@@ -0,0 +1,11 @@
|
|
1
|
+
FROM ruby:3.0.0
|
2
|
+
# RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
|
3
|
+
RUN mkdir /howzit
|
4
|
+
WORKDIR /howzit
|
5
|
+
# COPY ./ /howzit/
|
6
|
+
RUN gem install bundler:2.2.29
|
7
|
+
RUN apt-get update -y
|
8
|
+
RUN apt-get install -y less vim
|
9
|
+
COPY ./docker/inputrc /root/.inputrc
|
10
|
+
COPY ./docker/bash_profile /root/.bash_profile
|
11
|
+
CMD ["/planter/scripts/runtests.sh"]
|
data/docker/bash_profile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
export GLI_DEBUG=true
|
3
|
+
export EDITOR="/usr/bin/vim"
|
4
|
+
alias b="bundle exec bin/plant"
|
5
|
+
alias quit="exit"
|
6
|
+
|
7
|
+
shopt -s nocaseglob
|
8
|
+
shopt -s histappend
|
9
|
+
shopt -s histreedit
|
10
|
+
shopt -s histverify
|
11
|
+
shopt -s cmdhist
|
12
|
+
|
13
|
+
cd /planter
|
14
|
+
bundle install
|
15
|
+
gem install pkg/*.gem
|
data/docker/inputrc
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
"\e[3~": delete-char
|
2
|
+
"\ex": 'cd !$ \015ls\015'
|
3
|
+
"\ez": 'cd -\015'
|
4
|
+
"\e\C-m": '\C-a "$(\C-e|fzf)"\C-a'
|
5
|
+
"\e/": '"$(!!|fzf)"\C-a \C-m\C-m'
|
6
|
+
# these allow you to use alt+left/right arrow keys
|
7
|
+
# to jump the cursor over words
|
8
|
+
"\e[1;5C": forward-word
|
9
|
+
"\e[1;5D": backward-word
|
10
|
+
# "\e[D": backward-word
|
11
|
+
# "\e[C": forward-word
|
12
|
+
"\ea": menu-complete
|
13
|
+
# TAB: menu-complete
|
14
|
+
# "\e[Z": "\e-1\C-i"
|
15
|
+
|
16
|
+
"\e\C-l": history-and-alias-expand-line
|
17
|
+
|
18
|
+
# these allow you to start typing a command and
|
19
|
+
# use the up/down arrow to auto complete from
|
20
|
+
# commands in your history
|
21
|
+
"\e[B": history-search-forward
|
22
|
+
"\e[A": history-search-backward
|
23
|
+
"\ew": history-search-backward
|
24
|
+
"\es": history-search-forward
|
25
|
+
# this lets you hit tab to auto-complete a file or
|
26
|
+
# directory name ignoring case
|
27
|
+
set completion-ignore-case On
|
28
|
+
set mark-symlinked-directories On
|
29
|
+
set completion-prefix-display-length 2
|
30
|
+
set bell-style none
|
31
|
+
# set bell-style visible
|
32
|
+
set meta-flag on
|
33
|
+
set convert-meta off
|
34
|
+
set input-meta on
|
35
|
+
set output-meta on
|
36
|
+
set show-all-if-ambiguous on
|
37
|
+
set show-all-if-unmodified on
|
38
|
+
set completion-map-case on
|
39
|
+
set visible-stats on
|
40
|
+
|
41
|
+
# Do history expansion when space entered?
|
42
|
+
$if bash
|
43
|
+
Space: magic-space
|
44
|
+
$endif
|
45
|
+
|
46
|
+
# Show extra file information when completing, like `ls -F` does
|
47
|
+
set visible-stats on
|
48
|
+
|
49
|
+
# Be more intelligent when autocompleting by also looking at the text after
|
50
|
+
# the cursor. For example, when the current line is "cd ~/src/mozil", and
|
51
|
+
# the cursor is on the "z", pressing Tab will not autocomplete it to "cd
|
52
|
+
# ~/src/mozillail", but to "cd ~/src/mozilla". (This is supported by the
|
53
|
+
# Readline used by Bash 4.)
|
54
|
+
set skip-completed-text on
|
55
|
+
|
56
|
+
# Use Alt/Meta + Delete to delete the preceding word
|
57
|
+
"\e[3;3~": kill-word
|
data/lib/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: ../.rubocop.yml
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Planter
|
4
|
+
# Array helpers
|
5
|
+
class ::Array
|
6
|
+
##
|
7
|
+
## Convert an array of "(c)hoices" to abbrevation. If a default character is
|
8
|
+
## provided it will be highlighted. Output is a color template, unprocessed.
|
9
|
+
##
|
10
|
+
## @example ["(c)hoice", "(o)ther"].abbr_choices #=> "[c/o]"
|
11
|
+
##
|
12
|
+
## @param default [String] The color templated output string
|
13
|
+
##
|
14
|
+
def abbr_choices(default: nil)
|
15
|
+
chars = join(' ').scan(/\((.)\)/).map { |c| c[0] }
|
16
|
+
out = String.new
|
17
|
+
out << '{xdw}['
|
18
|
+
out << chars.map do |c|
|
19
|
+
if default && c.downcase == default.downcase
|
20
|
+
"{xbc}#{c}"
|
21
|
+
else
|
22
|
+
"{xbw}#{c}"
|
23
|
+
end
|
24
|
+
end.join('{dw}/')
|
25
|
+
out << '{dw}]{x}'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,370 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Planter
|
4
|
+
# Cribbed from <https://github.com/flori/term-ansicolor>
|
5
|
+
# Terminal output color functions.
|
6
|
+
module Color
|
7
|
+
# Regexp to match excape sequences
|
8
|
+
ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/
|
9
|
+
|
10
|
+
# All available color names. Available as methods and string extensions.
|
11
|
+
#
|
12
|
+
# @example Use a color as a method. Color reset will be added to end of string.
|
13
|
+
# Color.yellow('This text is yellow') => "\e[33mThis text is yellow\e[0m"
|
14
|
+
#
|
15
|
+
# @example Use a color as a string extension. Color reset added automatically.
|
16
|
+
# 'This text is green'.green => "\e[1;32mThis text is green\e[0m"
|
17
|
+
#
|
18
|
+
# @example Send a text string as a color
|
19
|
+
# Color.send('red') => "\e[31m"
|
20
|
+
ATTRIBUTES = [
|
21
|
+
[:clear, 0], # String#clear is already used to empty string in Ruby 1.9
|
22
|
+
[:reset, 0], # synonym for :clear
|
23
|
+
[:bold, 1],
|
24
|
+
[:dark, 2],
|
25
|
+
[:italic, 3], # not widely implemented
|
26
|
+
[:underline, 4],
|
27
|
+
[:underscore, 4], # synonym for :underline
|
28
|
+
[:blink, 5],
|
29
|
+
[:rapid_blink, 6], # not widely implemented
|
30
|
+
[:negative, 7], # no reverse because of String#reverse
|
31
|
+
[:concealed, 8],
|
32
|
+
[:strikethrough, 9], # not widely implemented
|
33
|
+
[:strike, 9], # not widely implemented
|
34
|
+
[:black, 30],
|
35
|
+
[:red, 31],
|
36
|
+
[:green, 32],
|
37
|
+
[:yellow, 33],
|
38
|
+
[:blue, 34],
|
39
|
+
[:magenta, 35],
|
40
|
+
[:purple, 35],
|
41
|
+
[:cyan, 36],
|
42
|
+
[:white, 37],
|
43
|
+
[:bgblack, 40],
|
44
|
+
[:bgred, 41],
|
45
|
+
[:bggreen, 42],
|
46
|
+
[:bgyellow, 43],
|
47
|
+
[:bgblue, 44],
|
48
|
+
[:bgmagenta, 45],
|
49
|
+
[:bgpurple, 45],
|
50
|
+
[:bgcyan, 46],
|
51
|
+
[:bgwhite, 47],
|
52
|
+
[:boldblack, 90],
|
53
|
+
[:boldred, 91],
|
54
|
+
[:boldgreen, 92],
|
55
|
+
[:boldyellow, 93],
|
56
|
+
[:boldblue, 94],
|
57
|
+
[:boldmagenta, 95],
|
58
|
+
[:boldpurple, 95],
|
59
|
+
[:boldcyan, 96],
|
60
|
+
[:boldwhite, 97],
|
61
|
+
[:boldbgblack, 100],
|
62
|
+
[:boldbgred, 101],
|
63
|
+
[:boldbggreen, 102],
|
64
|
+
[:boldbgyellow, 103],
|
65
|
+
[:boldbgblue, 104],
|
66
|
+
[:boldbgmagenta, 105],
|
67
|
+
[:boldbgpurple, 105],
|
68
|
+
[:boldbgcyan, 106],
|
69
|
+
[:boldbgwhite, 107],
|
70
|
+
[:softpurple, '0;35;40'],
|
71
|
+
[:hotpants, '7;34;40'],
|
72
|
+
[:knightrider, '7;30;40'],
|
73
|
+
[:flamingo, '7;31;47'],
|
74
|
+
[:yeller, '1;37;43'],
|
75
|
+
[:whiteboard, '1;30;47'],
|
76
|
+
[:chalkboard, '1;37;40'],
|
77
|
+
[:led, '0;32;40'],
|
78
|
+
[:redacted, '0;30;40'],
|
79
|
+
[:alert, '1;31;43'],
|
80
|
+
[:error, '1;37;41'],
|
81
|
+
[:default, '0;39']
|
82
|
+
].map(&:freeze).freeze
|
83
|
+
|
84
|
+
# Array of attribute keys only
|
85
|
+
ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
|
86
|
+
|
87
|
+
# Returns true if Color supports the +feature+.
|
88
|
+
#
|
89
|
+
# The feature :clear, that is mixing the clear color attribute into String,
|
90
|
+
# is only supported on ruby implementations, that do *not* already
|
91
|
+
# implement the String#clear method. It's better to use the reset color
|
92
|
+
# attribute instead.
|
93
|
+
def support?(feature)
|
94
|
+
case feature
|
95
|
+
when :clear
|
96
|
+
!String.instance_methods(false).map(&:to_sym).include?(:clear)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Template coloring
|
101
|
+
class ::String
|
102
|
+
##
|
103
|
+
## Shortcut for #template
|
104
|
+
##
|
105
|
+
## @return [String] colorized string
|
106
|
+
##
|
107
|
+
def x
|
108
|
+
Color.template(self)
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
## Extract the longest valid %color name from a string.
|
113
|
+
##
|
114
|
+
## Allows %colors to bleed into other text and still
|
115
|
+
## be recognized, e.g. %greensomething still finds
|
116
|
+
## %green.
|
117
|
+
##
|
118
|
+
## @return [String] a valid color name
|
119
|
+
##
|
120
|
+
def validate_color
|
121
|
+
valid_color = nil
|
122
|
+
compiled = ''
|
123
|
+
normalize_color.chars.each do |char|
|
124
|
+
compiled += char
|
125
|
+
if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
|
126
|
+
valid_color = compiled
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
valid_color
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
## Normalize a color name, removing underscores,
|
135
|
+
## replacing "bright" with "bold", and converting
|
136
|
+
## bgbold to boldbg
|
137
|
+
##
|
138
|
+
## @return [String] Normalized color name
|
139
|
+
##
|
140
|
+
def normalize_color
|
141
|
+
delete('_').sub(/bright/i, 'bold').sub(/bgbold/, 'boldbg')
|
142
|
+
end
|
143
|
+
|
144
|
+
# Get the calculated ANSI color at the end of the
|
145
|
+
# string
|
146
|
+
#
|
147
|
+
# @return ANSI escape sequence to match color
|
148
|
+
#
|
149
|
+
def last_color_code
|
150
|
+
m = scan(ESCAPE_REGEX)
|
151
|
+
|
152
|
+
em = ['0']
|
153
|
+
fg = nil
|
154
|
+
bg = nil
|
155
|
+
rgbf = nil
|
156
|
+
rgbb = nil
|
157
|
+
|
158
|
+
m.each do |c|
|
159
|
+
case c
|
160
|
+
when '0'
|
161
|
+
em = ['0']
|
162
|
+
fg, bg, rgbf, rgbb = nil
|
163
|
+
when /^[34]8/
|
164
|
+
case c
|
165
|
+
when /^3/
|
166
|
+
fg = nil
|
167
|
+
rgbf = c
|
168
|
+
when /^4/
|
169
|
+
bg = nil
|
170
|
+
rgbb = c
|
171
|
+
end
|
172
|
+
else
|
173
|
+
c.split(';').each do |i|
|
174
|
+
x = i.to_i
|
175
|
+
if x <= 9
|
176
|
+
em << x
|
177
|
+
elsif x >= 30 && x <= 39
|
178
|
+
rgbf = nil
|
179
|
+
fg = x
|
180
|
+
elsif x >= 40 && x <= 49
|
181
|
+
rgbb = nil
|
182
|
+
bg = x
|
183
|
+
elsif x >= 90 && x <= 97
|
184
|
+
rgbf = nil
|
185
|
+
fg = x
|
186
|
+
elsif x >= 100 && x <= 107
|
187
|
+
rgbb = nil
|
188
|
+
bg = x
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
escape = "\e[#{em.join(';')}m"
|
195
|
+
escape += "\e[#{rgbb}m" if rgbb
|
196
|
+
escape += "\e[#{rgbf}m" if rgbf
|
197
|
+
escape + "\e[#{[fg, bg].delete_if(&:nil?).join(';')}m"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class << self
|
202
|
+
# Returns true if the coloring function of this module
|
203
|
+
# is switched on, false otherwise.
|
204
|
+
def coloring?
|
205
|
+
@coloring
|
206
|
+
end
|
207
|
+
|
208
|
+
attr_writer :coloring
|
209
|
+
|
210
|
+
##
|
211
|
+
## Enables colored output
|
212
|
+
##
|
213
|
+
## @example Turn color on or off based on TTY
|
214
|
+
## Color.coloring = STDOUT.isatty
|
215
|
+
def coloring
|
216
|
+
@coloring ||= true
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
## Convert a template string to a colored string.
|
221
|
+
## Colors are specified with single letters inside
|
222
|
+
## curly braces. Uppercase changes background color.
|
223
|
+
##
|
224
|
+
## w: white, k: black, g: green, l: blue, y: yellow, c: cyan,
|
225
|
+
## m: magenta, r: red, b: bold, u: underline, i: italic,
|
226
|
+
## x: reset (remove background, color, emphasis)
|
227
|
+
##
|
228
|
+
## Also accepts {(#)RGB} and {(#)RRGGBB} strings. Put a b before
|
229
|
+
## the hash to make it a background color
|
230
|
+
##
|
231
|
+
## @example Convert a templated string
|
232
|
+
## Color.template('{Rwb}Warning:{x} {w}you look a little {g}ill{x}')
|
233
|
+
##
|
234
|
+
## @example Convert using RGB colors
|
235
|
+
## Color.template('{#f0a}This is an RGB color')
|
236
|
+
##
|
237
|
+
## @param input [String, Array] The template
|
238
|
+
## string. If this is an array, the
|
239
|
+
## elements will be joined with a
|
240
|
+
## space.
|
241
|
+
##
|
242
|
+
## @return [String] Colorized string
|
243
|
+
##
|
244
|
+
def template(input)
|
245
|
+
input = input.join(' ') if input.is_a? Array
|
246
|
+
return input.gsub(/(?<!\\)\{(\w+)\}/i, '') unless Color.coloring?
|
247
|
+
|
248
|
+
input = input.gsub(/(?<!\\)\{((?:[fb]g?)?#[a-f0-9]{3,6})\}/i) do
|
249
|
+
hex = Regexp.last_match(1)
|
250
|
+
rgb(hex)
|
251
|
+
end
|
252
|
+
|
253
|
+
fmt = input.gsub(/%/, '%%')
|
254
|
+
fmt = fmt.gsub(/(?<!\\)\{(\w+)\}/i) do
|
255
|
+
Regexp.last_match(1).chars.map { |c| "%<#{c}>s" }.join('')
|
256
|
+
end
|
257
|
+
|
258
|
+
colors = { w: white, k: black, g: green, l: blue,
|
259
|
+
y: yellow, c: cyan, m: magenta, r: red,
|
260
|
+
W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
|
261
|
+
Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
|
262
|
+
d: dark, b: bold, u: underline, i: italic, x: reset }
|
263
|
+
|
264
|
+
format(fmt, colors)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
ATTRIBUTES.each do |c, v|
|
269
|
+
new_method = <<-EOSCRIPT
|
270
|
+
# Color string as #{c}
|
271
|
+
def #{c}(string = nil)
|
272
|
+
result = ''
|
273
|
+
result << "\e[#{v}m" if Color.coloring?
|
274
|
+
if block_given?
|
275
|
+
result << yield
|
276
|
+
elsif string.respond_to?(:to_str)
|
277
|
+
result << string.to_str
|
278
|
+
elsif respond_to?(:to_str)
|
279
|
+
result << to_str
|
280
|
+
else
|
281
|
+
return result #only switch on
|
282
|
+
end
|
283
|
+
result << "\e[0m" if Color.coloring?
|
284
|
+
result
|
285
|
+
end
|
286
|
+
EOSCRIPT
|
287
|
+
|
288
|
+
module_eval(new_method)
|
289
|
+
|
290
|
+
next unless /bold/.match?(c)
|
291
|
+
|
292
|
+
# Accept brightwhite in addition to boldwhite
|
293
|
+
new_method = <<-EOSCRIPT
|
294
|
+
# color string as #{c}
|
295
|
+
def #{c.to_s.sub(/bold/, 'bright')}(string = nil)
|
296
|
+
result = ''
|
297
|
+
result << "\e[#{v}m" if Color.coloring?
|
298
|
+
if block_given?
|
299
|
+
result << yield
|
300
|
+
elsif string.respond_to?(:to_str)
|
301
|
+
result << string.to_str
|
302
|
+
elsif respond_to?(:to_str)
|
303
|
+
result << to_str
|
304
|
+
else
|
305
|
+
return result #only switch on
|
306
|
+
end
|
307
|
+
result << "\e[0m" if Color.coloring?
|
308
|
+
result
|
309
|
+
end
|
310
|
+
EOSCRIPT
|
311
|
+
|
312
|
+
module_eval(new_method)
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
## Generate escape codes for hex colors
|
317
|
+
##
|
318
|
+
## @param hex [String] The hexadecimal color code
|
319
|
+
##
|
320
|
+
## @return [String] ANSI escape string
|
321
|
+
##
|
322
|
+
def rgb(hex)
|
323
|
+
is_bg = /^bg?#/.match?(hex)
|
324
|
+
hex_string = hex.sub(/^([fb]g?)?#/, '')
|
325
|
+
|
326
|
+
if hex_string.length == 3
|
327
|
+
parts = hex_string.match(/(?<r>.)(?<g>.)(?<b>.)/)
|
328
|
+
|
329
|
+
t = []
|
330
|
+
%w[r g b].each do |e|
|
331
|
+
t << parts[e]
|
332
|
+
t << parts[e]
|
333
|
+
end
|
334
|
+
hex_string = t.join('')
|
335
|
+
end
|
336
|
+
|
337
|
+
parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
|
338
|
+
t = []
|
339
|
+
%w[r g b].each do |e|
|
340
|
+
t << parts[e].hex
|
341
|
+
end
|
342
|
+
|
343
|
+
"\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
|
344
|
+
end
|
345
|
+
|
346
|
+
# Regular expression that is used to scan for ANSI-sequences while
|
347
|
+
# uncoloring strings.
|
348
|
+
COLORED_REGEXP = /\e\[(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+m/
|
349
|
+
|
350
|
+
# Returns an uncolored version of the string, that is all
|
351
|
+
# ANSI-sequences are stripped from the string.
|
352
|
+
def uncolor(string = nil) # :yields:
|
353
|
+
if block_given?
|
354
|
+
yield.to_str.gsub(COLORED_REGEXP, '')
|
355
|
+
elsif string.respond_to?(:to_str)
|
356
|
+
string.to_str.gsub(COLORED_REGEXP, '')
|
357
|
+
elsif respond_to?(:to_str)
|
358
|
+
to_str.gsub(COLORED_REGEXP, '')
|
359
|
+
else
|
360
|
+
''
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Returns an array of all Color attributes as symbols.
|
365
|
+
def attributes
|
366
|
+
ATTRIBUTE_NAMES
|
367
|
+
end
|
368
|
+
extend self
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Planter
|
4
|
+
## Error handlers
|
5
|
+
module Errors
|
6
|
+
## Exit codes
|
7
|
+
EXIT_CODES = {
|
8
|
+
argument: 12,
|
9
|
+
canceled: 1,
|
10
|
+
config: 127,
|
11
|
+
git: 129
|
12
|
+
}.deep_freeze
|
13
|
+
|
14
|
+
## Argument error class
|
15
|
+
class InputError < StandardError
|
16
|
+
def initialize(msg = nil)
|
17
|
+
msg = msg ? "Input: #{msg}" : 'Canceled'
|
18
|
+
|
19
|
+
Planter.notify(msg, :error, exit_code: EXIT_CODES[:canceled])
|
20
|
+
|
21
|
+
super(msg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
## Argument error class
|
26
|
+
class ArgumentError < StandardError
|
27
|
+
def initialize(msg = nil)
|
28
|
+
msg = msg ? "Argument error: #{msg}" : 'Argument error'
|
29
|
+
|
30
|
+
Planter.notify(msg, :error, exit_code: EXIT_CODES[:argument])
|
31
|
+
|
32
|
+
super(msg)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
## Git error class
|
37
|
+
class GitError < StandardError
|
38
|
+
def initialize(msg = nil)
|
39
|
+
msg = msg ? "Git: #{msg}" : 'Git error'
|
40
|
+
|
41
|
+
Planter.spinner.error('(Error)')
|
42
|
+
Planter.notify(msg, :error, exit_code: EXIT_CODES[:git])
|
43
|
+
|
44
|
+
super(msg)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Configuration error class
|
49
|
+
class ConfigError < StandardError
|
50
|
+
def initialize(msg = nil)
|
51
|
+
msg = msg ? "Config: #{msg}" : 'Configuration error'
|
52
|
+
Planter.spinner.error('(Error)')
|
53
|
+
Planter.notify(msg, :error, exit_code: EXIT_CODES[:config])
|
54
|
+
|
55
|
+
super(msg)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|