blufin-lib 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/blufin-lib.rb +22 -0
- data/lib/blufin-lib/core/arrays.rb +18 -0
- data/lib/blufin-lib/core/browser.rb +30 -0
- data/lib/blufin-lib/core/datetime_utils.rb +14 -0
- data/lib/blufin-lib/core/encryptor.rb +53 -0
- data/lib/blufin-lib/core/files.rb +385 -0
- data/lib/blufin-lib/core/network.rb +25 -0
- data/lib/blufin-lib/core/numbers.rb +15 -0
- data/lib/blufin-lib/core/routes.rb +85 -0
- data/lib/blufin-lib/core/ssh.rb +145 -0
- data/lib/blufin-lib/core/strings.rb +109 -0
- data/lib/blufin-lib/core/terminal.rb +413 -0
- data/lib/blufin-lib/core/tools.rb +108 -0
- data/lib/blufin-lib/test/TestEnvironmentValidator.rb +98 -0
- data/lib/version.rb +1 -0
- metadata +100 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
module Blufin
|
4
|
+
|
5
|
+
class Network
|
6
|
+
|
7
|
+
# Checks if internet connection is present.
|
8
|
+
# @return boolean|void
|
9
|
+
def self.check_machine_is_online
|
10
|
+
|
11
|
+
begin
|
12
|
+
|
13
|
+
return true if open('http://www.google.com')
|
14
|
+
|
15
|
+
rescue
|
16
|
+
|
17
|
+
Blufin::Terminal::error('No internet connection', 'This script requires an internet connection and your machine is not online.')
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Numbers
|
4
|
+
|
5
|
+
# Add's comma's to numbers greater than 1000.
|
6
|
+
# Code from: https://dzone.com/articles/format-number-thousands
|
7
|
+
# @return String
|
8
|
+
def self.add_commas(number)
|
9
|
+
whole_part, decimal_part = number.to_s.split('.')
|
10
|
+
[whole_part.gsub(/(\d)(?=\d{3}+$)/, '\1,'), decimal_part].compact.join('.')
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Routes
|
4
|
+
|
5
|
+
# Returns number of flags set for route (from @opts variable)
|
6
|
+
# @param Array (@opts)
|
7
|
+
# @return int
|
8
|
+
def self.flags_set(opts)
|
9
|
+
flags_set = 0
|
10
|
+
opts.each do |key, value|
|
11
|
+
# Only here to prevent code-inspector from complaining.
|
12
|
+
key.strip! if key.nil?
|
13
|
+
if value
|
14
|
+
flags_set = flags_set + 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
if flags_set > 0
|
18
|
+
flags_set = flags_set / 2
|
19
|
+
end
|
20
|
+
flags_set
|
21
|
+
end
|
22
|
+
|
23
|
+
# Will throw an error if more than one flag is set.
|
24
|
+
# @param Array (@opts)
|
25
|
+
# @return void
|
26
|
+
def self.max_one_flag(opts)
|
27
|
+
all_flags = []
|
28
|
+
opts.each_key do |key|
|
29
|
+
unless key.to_s =~ /_given\z/i || key == :version || key == :help
|
30
|
+
all_flags << key.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
if flags_set(opts) >= 2
|
34
|
+
error_text = ['Please set 1 one of the following flags:', nil]
|
35
|
+
all_flags.each do |flag|
|
36
|
+
error_text << " \x1B[38;5;244m--#{convert_underscore_to_hypen(flag)}\x1B[0m"
|
37
|
+
end
|
38
|
+
Blufin::Terminal::error("You can set #{Blufin::Terminal::format_highlight('only one')} flag at a time", error_text)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Will throw an error if no flags are set.
|
43
|
+
# @param Array (@opts)
|
44
|
+
# @return void
|
45
|
+
def self.at_least_one_flag(opts)
|
46
|
+
all_flags = []
|
47
|
+
opts.each_key do |key|
|
48
|
+
unless key.to_s =~ /_given\z/i || key == :version || key == :help
|
49
|
+
all_flags << key.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if flags_set(opts) <= 0
|
53
|
+
error_text = ['Available flags are:', nil]
|
54
|
+
all_flags.each do |flag|
|
55
|
+
error_text << " \x1B[38;5;244m--#{convert_underscore_to_hypen(flag.to_s)}\x1B[0m"
|
56
|
+
end
|
57
|
+
Blufin::Terminal::error("You must set #{Blufin::Terminal::format_highlight('atleast one')} flag", error_text)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Will throw an error if more than 1 of the possible flags is set.
|
62
|
+
# Having NONE of the flags set is OK.
|
63
|
+
# @return void
|
64
|
+
def self.only_one_of(opts, possible_flags = [])
|
65
|
+
raise RuntimeError, "Expected Array, instead got: #{possible_flags.class}" unless possible_flags.is_a?(Array)
|
66
|
+
raise RuntimeError, 'possible_flags cannot be an empty Array.' unless possible_flags.any?
|
67
|
+
flags_set = 0
|
68
|
+
possible_flags.each { |possible_flag|
|
69
|
+
raise RuntimeError, "@opts does not contain flag: #{possible_flag}" unless opts.has_key?(possible_flag)
|
70
|
+
flags_set += 1 if opts[possible_flag]
|
71
|
+
}
|
72
|
+
Blufin::Terminal::error("Cannot set #{Blufin::Terminal::format_highlight('more than one')} of the following flags: \x1B[38;5;172m#{possible_flags.join(', ')}\x1B[0m") if flags_set > 1
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Does exactly what it says on the Tin.
|
78
|
+
# @return string
|
79
|
+
def self.convert_underscore_to_hypen(flag)
|
80
|
+
flag.gsub('_', '-')
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class SSH
|
4
|
+
|
5
|
+
SSH_PREFIX = 'ssh_'
|
6
|
+
|
7
|
+
# SSH into remote host.
|
8
|
+
# @return void
|
9
|
+
def self.ssh_into_remote(config_key)
|
10
|
+
if config_key.nil?
|
11
|
+
show_available_ssh_hosts
|
12
|
+
else
|
13
|
+
ssh_user, ssh_host, ssh_cert = get_ssh_credentials(config_key)
|
14
|
+
ssh_into_remote_credentials(ssh_user, ssh_host, ssh_cert)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# SSH into remote with pre-supplied credentials.
|
19
|
+
# @return void
|
20
|
+
def self.ssh_into_remote_credentials(ssh_user, ssh_host, ssh_cert = nil)
|
21
|
+
if ssh_cert.nil?
|
22
|
+
Blufin::Terminal::command("ssh #{ssh_user}@#{ssh_host}", nil, false, false)
|
23
|
+
else
|
24
|
+
Blufin::Terminal::command("ssh -i #{ssh_cert} #{ssh_user}@#{ssh_host}", nil, false, false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Source can be a file or directory but whatever it is, target must be the same (a file or directory).
|
29
|
+
# @return void
|
30
|
+
def self.scp_to_remote(config_key, source, target)
|
31
|
+
if config_key.nil?
|
32
|
+
show_available_ssh_hosts
|
33
|
+
else
|
34
|
+
ssh_user, ssh_host, ssh_cert = get_ssh_credentials(config_key)
|
35
|
+
unless Blufin::Files::file_exists(source)
|
36
|
+
Blufin::Terminal::error("The file doesn't exist: #{Blufin::Terminal::format_directory(source)}", nil, true)
|
37
|
+
return
|
38
|
+
end
|
39
|
+
Blufin::Terminal::command("scp -i #{ssh_cert} #{source} #{ssh_user}@#{ssh_host}:#{target}", nil)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
# Get SSH credentials from the config file.
|
46
|
+
# @return Array
|
47
|
+
def self.get_ssh_credentials(config_key)
|
48
|
+
ssh_parts = Blufin::Config.get_custom_key(SSH_PREFIX, config_key).split('|')
|
49
|
+
ssh_user = ssh_parts[0]
|
50
|
+
ssh_host = ssh_parts[1]
|
51
|
+
ssh_cert = ssh_parts[2].to_s.strip.length == 0 ? nil : ssh_parts[2]
|
52
|
+
help_message = [
|
53
|
+
"The file: #{Blufin::Terminal::format_directory(ConfigUnique::CONFIG_FILE)} must contain a line similar to the following:",
|
54
|
+
nil,
|
55
|
+
'ssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com|~/.ssh/pem-key.pem',
|
56
|
+
'ssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com',
|
57
|
+
]
|
58
|
+
Blufin::Terminal::error("SSH #{Blufin::Terminal::format_highlight('user')} required", help_message, true) if ssh_user.nil? || ssh_user == ''
|
59
|
+
Blufin::Terminal::error("SSH #{Blufin::Terminal::format_highlight('host')} required", help_message, true) if ssh_host.nil? || ssh_host == ''
|
60
|
+
unless ssh_cert.nil?
|
61
|
+
unless Blufin::Files::file_exists(File.expand_path(ssh_cert))
|
62
|
+
Blufin::Terminal::error("PEM key not found: #{Blufin::Terminal::format_directory(ssh_cert)}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return ssh_user, ssh_host, ssh_cert
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check that SSHPASS is installed.
|
69
|
+
# @return void
|
70
|
+
def self.check_sshpass_is_installed
|
71
|
+
Blufin::UtilsTools::check_sshpass_is_installed
|
72
|
+
end
|
73
|
+
|
74
|
+
# Runs a series of commands on the VM.
|
75
|
+
# @return void
|
76
|
+
def self.command(commands, ssh_user, ssh_host, ssh_cert, verbose = true)
|
77
|
+
commands = [commands] unless commands.is_a?(Array)
|
78
|
+
if commands.any?
|
79
|
+
commands.each do |command|
|
80
|
+
puts "\x1B[48;5;136m Executing \x1B[0m \xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("#{command} \x1B[0m\xe2\x86\x92 \x1B[38;5;202m[\x1B[38;5;168m#{ssh_user}@#{ssh_host}\x1B[38;5;202m]\x1B[0m")}" if verbose
|
81
|
+
if ssh_cert.nil?
|
82
|
+
Blufin::Terminal::command("ssh #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)
|
83
|
+
else
|
84
|
+
Blufin::Terminal::command("ssh -i #{ssh_cert} #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Runs a series of commands on the VM and capture output.
|
91
|
+
# Currently only works for single-line output (IE: Won't capture multi-line output properly).
|
92
|
+
# @return void
|
93
|
+
def self.command_capture(commands, ssh_user, ssh_host, ssh_cert, verbose = true)
|
94
|
+
commands = [commands] unless commands.is_a?(Array)
|
95
|
+
results = []
|
96
|
+
if commands.any?
|
97
|
+
commands.each do |command|
|
98
|
+
puts "\x1B[48;5;136m Executing \x1B[0m \xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("#{command} \x1B[0m\xe2\x86\x92 \x1B[38;5;202m[\x1B[38;5;168m#{ssh_user}@#{ssh_host}\x1B[38;5;202m]\x1B[0m")}" if verbose
|
99
|
+
if ssh_cert.nil?
|
100
|
+
results << Blufin::Terminal::command_capture("ssh #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)[0].gsub(/\n$/, '')
|
101
|
+
else
|
102
|
+
results << Blufin::Terminal::command_capture("ssh -i #{ssh_cert} #{ssh_user}@#{ssh_host} '#{command}'", nil, false, false)[0].gsub(/\n$/, '')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
results
|
107
|
+
end
|
108
|
+
|
109
|
+
# Show a list of available SSH hosts.
|
110
|
+
# @return void
|
111
|
+
def self.show_available_ssh_hosts
|
112
|
+
all_keys = Blufin::Config::params
|
113
|
+
ssh_hosts = []
|
114
|
+
ssh_hosts_lengths = []
|
115
|
+
all_keys.keys.each do |key|
|
116
|
+
if key =~ /\A#{SSH_PREFIX}.+/i
|
117
|
+
ssh_hosts_lengths << key.length
|
118
|
+
end
|
119
|
+
end
|
120
|
+
if ssh_hosts_lengths.any?
|
121
|
+
ssh_hosts_lengths.uniq!
|
122
|
+
max_key_length = ssh_hosts_lengths.max - SSH_PREFIX.length
|
123
|
+
all_keys.keys.sort!.each do |key|
|
124
|
+
if key =~ /\A#{SSH_PREFIX}.+/i
|
125
|
+
ssh_user, ssh_host, ssh_cert = get_ssh_credentials(key.gsub(SSH_PREFIX, ''))
|
126
|
+
ssh_hosts << "\x1B[38;5;154m#{key.gsub(SSH_PREFIX, '').rjust(max_key_length, ' ')}\x1B[0m\x1B[38;5;240m \xe2\x80\x94 #{ssh_host} \x1B[38;5;245m(#{ssh_user})#{ssh_cert.nil? ? '' : "\x1B[38;5;204m #{ssh_cert}"}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
Blufin::Terminal::info('Pre-configured hosts to SSH into:', ssh_hosts)
|
130
|
+
else
|
131
|
+
help_message = [
|
132
|
+
"To define a remote host \xe2\x80\x94 run #{Blufin::Terminal::format_command("#{ConfigUnique::GEM_NAME} x")} and add a line similar to:",
|
133
|
+
nil,
|
134
|
+
"\x1B[38;5;154mssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com|~/.ssh/pem-key.pem",
|
135
|
+
"\x1B[38;5;154mssh_ec2=ec2-user|ec2-XX-XX-XX-XX.eu-west-1.compute.amazonaws.com",
|
136
|
+
nil,
|
137
|
+
"IMPORTANT: Configuration key must start with prefix: #{Blufin::Terminal::format_highlight(SSH_PREFIX)}"
|
138
|
+
]
|
139
|
+
Blufin::Terminal::info('No remote host(s) defined in configuration file', help_message)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Blufin
|
2
|
+
|
3
|
+
class Strings
|
4
|
+
|
5
|
+
# Convert 'snake_case' or 'SnAKE_cAse' to 'SnakeCase'.
|
6
|
+
# @param String
|
7
|
+
# @return String
|
8
|
+
def self.snake_case_to_camel_case(string)
|
9
|
+
validate_string(string)
|
10
|
+
string = string.downcase
|
11
|
+
return string if string !~ /_/ && string =~ /[A-Z]+.*/
|
12
|
+
string.split('_').map { |e| e.capitalize }.join
|
13
|
+
end
|
14
|
+
|
15
|
+
# Convert 'snake_case' or 'SnAKE_cAse' to 'snakeCase'.
|
16
|
+
# @param String
|
17
|
+
# @return String
|
18
|
+
def self.snake_case_to_camel_case_lower(string)
|
19
|
+
string = snake_case_to_camel_case(string)
|
20
|
+
"#{string[0, 1].downcase}#{string[1..-1]}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Convert 'CamelCase' to 'snake_case'
|
24
|
+
# @param String
|
25
|
+
# @return String
|
26
|
+
def self.camel_case_to_snake_case(string)
|
27
|
+
string.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').tr('-', '_').downcase
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remove preceding/trailing slashes from a string (and trim preceding/trailing whitespace).
|
31
|
+
# @param String - The string to be trimmed (and returned).
|
32
|
+
# @return String
|
33
|
+
def self.remove_surrounding_slashes(string)
|
34
|
+
raise RuntimeError, "Expected String, instead got:#{string.class}" unless string.is_a?(String)
|
35
|
+
string = string.strip
|
36
|
+
validate_string(string)
|
37
|
+
string = string.gsub(/\A\/+/, '')
|
38
|
+
string = string.gsub(/\/+\z/, '')
|
39
|
+
string
|
40
|
+
end
|
41
|
+
|
42
|
+
# Extracts parts of a string. Regex is what to match, array is what to remove (gsub) afterwards.
|
43
|
+
# @return boolean
|
44
|
+
def self.extract_using_regex(string, regex, strings_to_remove)
|
45
|
+
unless strings_to_remove.is_a?(Array)
|
46
|
+
raise RuntimeError, "'strings_to_remove' must be an Array. You passed: #{strings_to_remove.class}"
|
47
|
+
|
48
|
+
end
|
49
|
+
match = string.match(regex)
|
50
|
+
raise RuntimeError, "No match found for '#{string}' and regex '#{regex}'." if match.nil?
|
51
|
+
match_to_return = match[0]
|
52
|
+
strings_to_remove.each do |string_to_remove|
|
53
|
+
match_to_return = match_to_return.gsub(string_to_remove, '')
|
54
|
+
end
|
55
|
+
match_to_return
|
56
|
+
end
|
57
|
+
|
58
|
+
# Finds the difference between 2 string
|
59
|
+
# Anything up to 0.15 means they're fairly similar.
|
60
|
+
# @return Float
|
61
|
+
def self.string_difference_percent(a, b)
|
62
|
+
validate_string(a)
|
63
|
+
validate_string(b)
|
64
|
+
x = a.dup.gsub(' ', '')
|
65
|
+
y = b.dup.gsub(' ', '')
|
66
|
+
x_hash = string_difference_as_hash(x)
|
67
|
+
y_hash = string_difference_as_hash(y)
|
68
|
+
total_letters = x.length + y.length
|
69
|
+
x_hash.each do |key, value|
|
70
|
+
if y_hash.has_key?(key)
|
71
|
+
if y_hash[key] == x_hash[key]
|
72
|
+
y_hash.delete(key)
|
73
|
+
else
|
74
|
+
y_hash[key] = (x_hash[key].to_i - y_hash[key].to_i).abs
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
discrepancies = 0
|
79
|
+
y_hash.each do |key, value|
|
80
|
+
discrepancies = discrepancies + value.to_i
|
81
|
+
end
|
82
|
+
return ((discrepancies.to_f / total_letters.to_f) * 100).round
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# Internal string validation.
|
88
|
+
# @return void
|
89
|
+
def self.validate_string(string)
|
90
|
+
raise RuntimeError, "Expected String, instead got:#{string.class}" if string.nil? || !string.is_a?(String)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Internal method helper for string_difference_percent().
|
94
|
+
# @return Hash
|
95
|
+
def self.string_difference_as_hash(string)
|
96
|
+
hash = {}
|
97
|
+
string.split('').each do |letter|
|
98
|
+
if !hash.has_key?(letter)
|
99
|
+
hash[letter] = 1
|
100
|
+
else
|
101
|
+
hash[letter] = hash[letter].to_i + 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
hash
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,413 @@
|
|
1
|
+
require 'highline/import'
|
2
|
+
require 'columnist'
|
3
|
+
require 'readline'
|
4
|
+
require 'tty-spinner'
|
5
|
+
|
6
|
+
module Blufin
|
7
|
+
|
8
|
+
class Terminal
|
9
|
+
|
10
|
+
extend Columnist
|
11
|
+
|
12
|
+
MSG_INFO = 'info'
|
13
|
+
MSG_WARNING = 'warning'
|
14
|
+
MSG_ERROR = 'error'
|
15
|
+
MSG_TODO = 'todo'
|
16
|
+
MSG_AUTOMATIC = 'automatic'
|
17
|
+
MSG_GENERATED = 'generated'
|
18
|
+
MSG_PROCESSED = 'processed'
|
19
|
+
MSG_PROGRESS = 'progress'
|
20
|
+
MSG_CUSTOM = 'custom'
|
21
|
+
MSG_CUSTOM_AUTO_PAD = 'custom_auto_pad'
|
22
|
+
|
23
|
+
# Runs a series of commands in the terminal.
|
24
|
+
# @return void
|
25
|
+
def self.command(commands, location = nil, verbose = true, verbose_cd = true, capture_real_output = false)
|
26
|
+
unless commands.is_a?(Array)
|
27
|
+
commands = [commands]
|
28
|
+
end
|
29
|
+
unless location.nil?
|
30
|
+
unless File.directory?(File.expand_path(location))
|
31
|
+
error('Directory not found.', "Cannot find the following directory: \x1B[38;5;205m#{location}\x1B[0m", true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
output = []
|
35
|
+
if verbose_cd && verbose && commands.any? && !location.nil?
|
36
|
+
puts "\x1B[38;5;231m\x1B[42m Executing \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("cd #{location}")}\n"
|
37
|
+
end
|
38
|
+
if commands.any?
|
39
|
+
commands.each do |command|
|
40
|
+
if verbose
|
41
|
+
puts "\x1B[38;5;231m\x1B[42m Executing \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m #{Blufin::Terminal::format_command("#{command}")}\n"
|
42
|
+
end
|
43
|
+
if location.nil?
|
44
|
+
if capture_real_output
|
45
|
+
output << `#{command}`
|
46
|
+
else
|
47
|
+
output << system("#{command}")
|
48
|
+
end
|
49
|
+
else
|
50
|
+
if capture_real_output
|
51
|
+
output << `cd #{location} && #{command}`
|
52
|
+
else
|
53
|
+
output << system("cd #{location} && #{command}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if capture_real_output
|
59
|
+
output.map! { |v| v.gsub('\n', "\n") }
|
60
|
+
end
|
61
|
+
output
|
62
|
+
end
|
63
|
+
|
64
|
+
# Runs a series of commands in the terminal (and captures the output).
|
65
|
+
# @return void
|
66
|
+
def self.command_capture(commands, location = nil, verbose = true, verbose_cd = true)
|
67
|
+
command(commands, location, verbose, verbose_cd, true)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Executes a command and shows that something is happening (via a cli-spinner)
|
71
|
+
# See: https://github.com/piotrmurach/tty-spinner/blob/master/lib/tty/spinner/formats.rb (for spinner options).
|
72
|
+
# @return void
|
73
|
+
def self.execute(command, path, format = :dots)
|
74
|
+
|
75
|
+
# TODO NOW - Add execution time to this.
|
76
|
+
|
77
|
+
spinner = TTY::Spinner.new("[:spinner] \x1B[38;5;208m#{command} \x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;240m#{path}\x1B[0m", format: format)
|
78
|
+
spinner.auto_spin
|
79
|
+
res = system("cd #{path} && #{command} > /dev/null")
|
80
|
+
spinner.success("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;46mComplete\x1B[0m") if res
|
81
|
+
spinner.error("\x1B[38;5;246m\xe2\x86\x92 \x1B[38;5;196mFailed\x1B[0m") unless res
|
82
|
+
res
|
83
|
+
end
|
84
|
+
|
85
|
+
# Outputs messages to the terminal.
|
86
|
+
# If CUSTOM, title must be 11 characters to match existing scheme.
|
87
|
+
# @return void
|
88
|
+
def self.output(message = 'No message', type = MSG_INFO, title = nil, bg_color = nil)
|
89
|
+
|
90
|
+
raise RuntimeError, "'title' cannot be nil." if title.nil? && [MSG_CUSTOM, MSG_CUSTOM_AUTO_PAD].include?(type)
|
91
|
+
raise RuntimeError, "'bg_color' cannot be nil." if bg_color.nil? && [MSG_CUSTOM, MSG_CUSTOM_AUTO_PAD].include?(type)
|
92
|
+
raise RuntimeError, "'title' cannot be more that 9 characerts when MSG_CUSTOM_AUTO_PAD is the message type." if type == MSG_CUSTOM_AUTO_PAD && title.length > 9
|
93
|
+
|
94
|
+
case type
|
95
|
+
when MSG_INFO
|
96
|
+
puts "\x1B[38;5;231m\x1B[48;5;2m Executing \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
97
|
+
when MSG_WARNING
|
98
|
+
puts "\x1B[38;5;231m\x1B[48;5;202m Warning \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
99
|
+
when MSG_ERROR
|
100
|
+
puts "\x1B[38;5;231m\x1B[48;5;196m Error \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
101
|
+
when MSG_TODO
|
102
|
+
puts "\x1B[38;5;231m\x1B[48;5;199m @TODO \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
103
|
+
when MSG_AUTOMATIC
|
104
|
+
puts "\x1B[38;5;231m\x1B[48;5;96m Automatic \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
105
|
+
when MSG_GENERATED
|
106
|
+
puts "\x1B[38;5;231m\x1B[48;5;96m Generated \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
107
|
+
when MSG_PROCESSED
|
108
|
+
puts "\x1B[38;5;231m\x1B[48;5;238m Processed \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
109
|
+
when MSG_PROGRESS
|
110
|
+
puts " \x1B[38;5;240m#{message}\x1B[0m\n"
|
111
|
+
when MSG_CUSTOM
|
112
|
+
puts "\x1B[38;5;231m\x1B[48;5;#{bg_color}m#{title}\x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
113
|
+
when MSG_CUSTOM_AUTO_PAD
|
114
|
+
puts "\x1B[38;5;231m#{''.rjust((9 - title.length), ' ')}\x1B[48;5;#{bg_color}m #{title} \x1B[0m \x1B[0m\xe2\x86\x92\x1B[0m \x1B[0m#{message}\x1B[0m\n"
|
115
|
+
else
|
116
|
+
puts "'#{type}' is not a valid Terminal::output() type.\n"
|
117
|
+
exit
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Displays error and exits script by default.
|
122
|
+
# @return void
|
123
|
+
def self.abort(title = nil, message = nil, exit_script = true, preceding_blank_line = false)
|
124
|
+
title = 'Abandon ship!' if title.nil?
|
125
|
+
message = "You have chosen to \x1B[38;5;196mABORT\x1B[38;5;240m the script." if message.nil?
|
126
|
+
Blufin::Terminal::error(title, message, exit_script, preceding_blank_line, 'ABORT')
|
127
|
+
end
|
128
|
+
|
129
|
+
# Displays error and exits script by default.
|
130
|
+
# @return void
|
131
|
+
def self.error(title = nil, message = nil, exit_script = true, preceding_blank_line = true, error_text = 'Error')
|
132
|
+
puts if preceding_blank_line
|
133
|
+
puts " \x1B[38;5;231m\x1B[48;5;196m #{error_text} \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
134
|
+
parse_messages(message)
|
135
|
+
if exit_script
|
136
|
+
exit
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Displays automatic message.
|
141
|
+
# @return void
|
142
|
+
def self.automatic(title = nil, message = nil, preceding_blank_line = true)
|
143
|
+
puts if preceding_blank_line
|
144
|
+
puts "\x1B[38;5;231m\x1B[48;5;96m Automatic \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
145
|
+
parse_messages(message)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Displays info message.
|
149
|
+
# @return void
|
150
|
+
def self.info(title = nil, message = nil, preceding_blank_line = true)
|
151
|
+
puts if preceding_blank_line
|
152
|
+
puts " \x1B[38;5;231m\x1B[48;5;240m Message \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
153
|
+
parse_messages(message)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Displays success message.
|
157
|
+
# @return void
|
158
|
+
def self.success(title = nil, message = nil, preceding_blank_line = true)
|
159
|
+
puts if preceding_blank_line
|
160
|
+
puts " \x1B[38;5;231m\x1B[48;5;2m Success \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
161
|
+
parse_messages(message)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Displays warning message.
|
165
|
+
# @return void
|
166
|
+
def self.warning(title = nil, message = nil, preceding_blank_line = true)
|
167
|
+
puts if preceding_blank_line
|
168
|
+
puts " \x1B[38;5;231m\x1B[48;5;202m Warning \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
169
|
+
parse_messages(message)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Displays custom message (ideally, keyword should be 7 characters long to match the rest of the output).
|
173
|
+
# @return void
|
174
|
+
def self.custom(keyword = 'N/A', color = 1, title = nil, message = nil, preceding_blank_line = true)
|
175
|
+
puts if preceding_blank_line
|
176
|
+
puts " \x1B[38;5;231m\x1B[48;5;#{color}m #{keyword} \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
177
|
+
parse_messages(message)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Displays custom message with a progress indicator.
|
181
|
+
# @return void
|
182
|
+
def self.custom_progress(keyword = 'N/A', color = 1, title = nil, message = nil, preceding_blank_line = true)
|
183
|
+
puts if preceding_blank_line
|
184
|
+
puts " \x1B[38;5;231m\x1B[48;5;#{color}m #{keyword} \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
185
|
+
parse_messages(message)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns action message in consistent, uniform manner.
|
189
|
+
# @return String
|
190
|
+
def self.format_action(action_text, capitalize = true)
|
191
|
+
action_text = action_text.nil? ? 'N/A' : action_text
|
192
|
+
action_text = action_text.upcase if capitalize
|
193
|
+
"\x1B[38;5;170m#{action_text}\x1B[0m"
|
194
|
+
end
|
195
|
+
|
196
|
+
# Returns branch name in consistent, uniform manner.
|
197
|
+
# @return String
|
198
|
+
def self.format_branch(branch_name)
|
199
|
+
"\x1B[38;5;40m[#{branch_name}]\x1B[0m"
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns command name in consistent, uniform manner.
|
203
|
+
# @return String
|
204
|
+
def self.format_command(command_name)
|
205
|
+
"\x1B[38;5;220m#{command_name}\x1B[0m"
|
206
|
+
end
|
207
|
+
|
208
|
+
# Returns directory name in consistent, uniform manner.
|
209
|
+
# @return String
|
210
|
+
def self.format_directory(directory_name, expand_path = true)
|
211
|
+
return "\x1B[38;5;48m#{File.expand_path(directory_name)}\x1B[0m" if expand_path
|
212
|
+
return "\x1B[38;5;48m#{directory_name}\x1B[0m" unless expand_path
|
213
|
+
end
|
214
|
+
|
215
|
+
# Returns flag name in consistent, uniform manner.
|
216
|
+
# @return String
|
217
|
+
def self.format_flag(flag_letter, display_flag_text = true)
|
218
|
+
letter_array = []
|
219
|
+
if flag_letter.is_a? String
|
220
|
+
letter_array = Array[flag_letter]
|
221
|
+
elsif flag_letter.is_a? Array
|
222
|
+
letter_array = flag_letter
|
223
|
+
else
|
224
|
+
Blufin::Terminal::error('Terminal::format_flag expects either String or Array.', nil, true)
|
225
|
+
end
|
226
|
+
flag_txt = ''
|
227
|
+
letter_array.each do |letter|
|
228
|
+
flag_txt = "#{flag_txt}, -#{letter}"
|
229
|
+
end
|
230
|
+
xtra_txt = letter_array.length > 1 ? ' flags' : ' flag'
|
231
|
+
flag_txt = flag_txt[2..-1]
|
232
|
+
"\x1B[38;5;152m#{flag_txt}#{display_flag_text ? xtra_txt : ''}\x1B[0m"
|
233
|
+
end
|
234
|
+
|
235
|
+
# Returns action message in consistent, uniform manner.
|
236
|
+
# @return String
|
237
|
+
def self.format_highlight(highlighted_text, capitalize = false)
|
238
|
+
if capitalize
|
239
|
+
return "\x1B[38;5;117m#{highlighted_text.upcase}\x1B[0m"
|
240
|
+
else
|
241
|
+
return "\x1B[38;5;117m#{highlighted_text}\x1B[0m"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns invalid data in consistent, uniform manner.
|
246
|
+
# @return String
|
247
|
+
def self.format_invalid(invalid_string, capitalize = false)
|
248
|
+
if capitalize
|
249
|
+
return "\x1B[38;5;196m#{invalid_string.upcase}\x1B[0m"
|
250
|
+
else
|
251
|
+
return "\x1B[38;5;196m#{invalid_string}\x1B[0m"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Generates 'filler' string.
|
256
|
+
# @return String
|
257
|
+
def self.fill(length = 0, string = ' ')
|
258
|
+
fill_string = ''
|
259
|
+
(0..length - 1).each {
|
260
|
+
fill_string = "#{fill_string}#{string}"
|
261
|
+
}
|
262
|
+
fill_string
|
263
|
+
end
|
264
|
+
|
265
|
+
# Gives a prompt where 'y/Y' will return TRUE, 'n/N' will return false, and ANY other key will do nothing.
|
266
|
+
# @return void
|
267
|
+
def self.prompt_yes_no(title = nil, message = nil, confirmation_message = nil, preceding_blank_line = true)
|
268
|
+
title = 'Please confirm YES or NO.' if title.nil?
|
269
|
+
confirmation_message = 'Would you like to continue?' if confirmation_message.nil?
|
270
|
+
puts if preceding_blank_line
|
271
|
+
puts " \x1B[38;5;231m\x1B[48;5;56m Confirm \x1B[0m #{title.nil? ? '' : "\xe2\x86\x92 "}#{title}\n"
|
272
|
+
parse_messages(message)
|
273
|
+
response = ''
|
274
|
+
until %w[y Y n N x X a A].include? response
|
275
|
+
response = Readline::readline(" \x1B[38;5;161m#{confirmation_message} \x1B[0m[y/n]\x1B[90m => \x1B[0m", true)
|
276
|
+
end
|
277
|
+
case response.downcase
|
278
|
+
when 'y'
|
279
|
+
puts "\n"
|
280
|
+
return true
|
281
|
+
when 'n'
|
282
|
+
puts "\n"
|
283
|
+
return false
|
284
|
+
when 'a'
|
285
|
+
Blufin::Terminal::error('Abandon ship!', ["You have chosen to \x1B[38;5;9mABORT\x1B[38;5;240m the script.", nil, 'Please note that whenever you do this, any scripted tasks which were running', 'will have been interrupted mid-script. This may (or may not) cause problems.'], true)
|
286
|
+
when 'x'
|
287
|
+
Blufin::Terminal::error('Abandon ship!', ["You have chosen to \x1B[38;5;9mABORT\x1B[38;5;240m the script.", nil, 'Please note that whenever you do this, any scripted tasks which were running', 'will have been interrupted mid-script. This may (or may not) cause problems.'], true)
|
288
|
+
else
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Shows a prompt waiting for user input.
|
293
|
+
# If validation_proc is passed, the Proc must return TRUE in order for validation to pass.
|
294
|
+
# @return string
|
295
|
+
def self.prompt_for_input(message, subtitle_array = nil, nil_allowed = true, preceding_blank_line = true, clear_screen = true, validation_proc = nil, validation_message = 'Invalid value!')
|
296
|
+
system('clear') if clear_screen
|
297
|
+
puts if preceding_blank_line
|
298
|
+
puts " \x1B[38;5;231m\x1B[48;5;125m Question \x1B[0m \xe2\x86\x92 #{message}\n"
|
299
|
+
parse_messages(subtitle_array) unless subtitle_array.nil?
|
300
|
+
puts if subtitle_array.nil?
|
301
|
+
response = nil
|
302
|
+
while response.nil?
|
303
|
+
response = Readline::readline(" \x1B[38;5;245m=>\x1B[0m ", true)
|
304
|
+
unless nil_allowed
|
305
|
+
response.strip!
|
306
|
+
response = nil if response == '' || response.nil?
|
307
|
+
end
|
308
|
+
unless validation_proc.nil?
|
309
|
+
raise RuntimeError, "Expected validation_proc to be an instance of Proc, instead got: #{validation_proc.class}" unless validation_proc.is_a?(Proc)
|
310
|
+
unless validation_proc.call(response)
|
311
|
+
puts " \x1B[38;5;245m=>\x1B[0m \x1B[38;5;196mERROR\x1B[0m \xe2\x80\x94 #{validation_message}\n"
|
312
|
+
response = nil
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
puts
|
317
|
+
response
|
318
|
+
end
|
319
|
+
|
320
|
+
# Gives a prompt where ANY KEY will continue executing the script.
|
321
|
+
# @return void
|
322
|
+
def self.any_key_to_continue(text = nil, preceding_blank_line = true)
|
323
|
+
STDOUT.flush
|
324
|
+
puts if preceding_blank_line
|
325
|
+
text = 'Press any key to continue' if text.nil?
|
326
|
+
print " \x1B[38;5;240m#{text}\x1B[0m => "
|
327
|
+
STDIN.gets
|
328
|
+
puts
|
329
|
+
nil
|
330
|
+
end
|
331
|
+
|
332
|
+
# Gets the Terminal width
|
333
|
+
# @return Integer
|
334
|
+
def self.get_terminal_width
|
335
|
+
terminal_size = HighLine::SystemExtensions.terminal_size
|
336
|
+
terminal_size[0]
|
337
|
+
end
|
338
|
+
|
339
|
+
# Gets the Terminal height
|
340
|
+
# @return Integer
|
341
|
+
def self.get_terminal_height
|
342
|
+
terminal_size = HighLine::SystemExtensions.terminal_size
|
343
|
+
terminal_size[1]
|
344
|
+
end
|
345
|
+
|
346
|
+
# Outputs Exceptions in a pretty, standardized format.
|
347
|
+
# @return void
|
348
|
+
def self.print_exception(e)
|
349
|
+
if e.is_a?(Exception)
|
350
|
+
custom('Exception', 235, "There was an error in your code: \x1B[48;5;124m #{e.message} \x1B[0m", e.backtrace)
|
351
|
+
elsif e.is_a?(String)
|
352
|
+
custom('Exception', 235, "There was an error in your code: \x1B[48;5;124m #{e} \x1B[0m")
|
353
|
+
else
|
354
|
+
raise RuntimeError, "Expected String|Exception, instead got:#{e.class}"
|
355
|
+
end
|
356
|
+
exit 1
|
357
|
+
end
|
358
|
+
|
359
|
+
# Outputs Array of files/directories in a pretty, standardized format.
|
360
|
+
# @return void
|
361
|
+
def self.print_files_written(array_of_paths_or_files, message = nil, limit = 10, preceding_blank_line = true)
|
362
|
+
raise RuntimeError, "Expected Array of files, instead got: #{array_of_paths_or_files.class}" unless array_of_paths_or_files.is_a?(Array)
|
363
|
+
message = "Wrote the following #{Blufin::Terminal::format_highlight('files/directories')}" if message.nil?
|
364
|
+
limit = limit.nil? ? 99999999999 : limit.to_i
|
365
|
+
file_output = []
|
366
|
+
file_count = 0
|
367
|
+
file_more = 0
|
368
|
+
array_of_paths_or_files.compact!
|
369
|
+
array_of_paths_or_files.sort!
|
370
|
+
array_of_paths_or_files.each do |path_or_file|
|
371
|
+
file_count += 1
|
372
|
+
if file_count > limit
|
373
|
+
file_more += 1
|
374
|
+
next
|
375
|
+
end
|
376
|
+
if Blufin::Files::path_exists(path_or_file)
|
377
|
+
file_output << "Path: \x1B[38;5;248m#{path_or_file.strip}\x1B[0m"
|
378
|
+
else
|
379
|
+
file_output << "File: \x1B[38;5;46m#{path_or_file.strip}\x1B[0m"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
file_output << "\x1B[38;5;168m#{file_more} more...\x1B[0m" if file_more > 0
|
383
|
+
Blufin::Terminal::automatic(message, file_output, preceding_blank_line)
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
# Parses messages for various output functions.
|
389
|
+
# @return void
|
390
|
+
def self.parse_messages(message)
|
391
|
+
puts
|
392
|
+
unless message.nil?
|
393
|
+
if message.is_a?(Array)
|
394
|
+
messages = message
|
395
|
+
else
|
396
|
+
messages = message.split("\n")
|
397
|
+
end
|
398
|
+
table(:border => false) do
|
399
|
+
messages.each do |line|
|
400
|
+
new_line = line.nil? ? '' : line.to_s.gsub("\x1B[0m", "\x1B[38;5;240m")
|
401
|
+
row do
|
402
|
+
column('', :align => 'left', :width => 4)
|
403
|
+
column("\x1B[38;5;240m#{new_line}\x1B[0m", :align => 'left', :width => 180)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
puts
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|