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.
@@ -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