blufin-lib 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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