blufin-lib 1.4.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/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
|