tabry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/bin/tabry-bash +48 -0
  3. data/bin/tabry-help +17 -0
  4. data/bin/tabry-test-options +9 -0
  5. data/bin/tabry-test-parse +20 -0
  6. data/lib/tabry/cli/arg_proxy.rb +71 -0
  7. data/lib/tabry/cli/base.rb +34 -0
  8. data/lib/tabry/cli/builder.rb +104 -0
  9. data/lib/tabry/cli/flag_proxy.rb +45 -0
  10. data/lib/tabry/cli/internals.rb +10 -0
  11. data/lib/tabry/cli/util/config.rb +48 -0
  12. data/lib/tabry/cli/util.rb +51 -0
  13. data/lib/tabry/config_loader.rb +55 -0
  14. data/lib/tabry/core_ext/string/colors.rb +91 -0
  15. data/lib/tabry/machine.rb +124 -0
  16. data/lib/tabry/models/arg.rb +43 -0
  17. data/lib/tabry/models/arg_base.rb +10 -0
  18. data/lib/tabry/models/arg_include.rb +9 -0
  19. data/lib/tabry/models/arg_includes.rb +14 -0
  20. data/lib/tabry/models/args_list.rb +31 -0
  21. data/lib/tabry/models/config.rb +44 -0
  22. data/lib/tabry/models/config_error.rb +8 -0
  23. data/lib/tabry/models/config_list.rb +48 -0
  24. data/lib/tabry/models/config_object.rb +78 -0
  25. data/lib/tabry/models/config_string_hash.rb +44 -0
  26. data/lib/tabry/models/const_option.rb +17 -0
  27. data/lib/tabry/models/dir_option.rb +25 -0
  28. data/lib/tabry/models/file_option.rb +25 -0
  29. data/lib/tabry/models/flag.rb +55 -0
  30. data/lib/tabry/models/flags_list.rb +47 -0
  31. data/lib/tabry/models/include_arg.rb +18 -0
  32. data/lib/tabry/models/include_flag.rb +18 -0
  33. data/lib/tabry/models/include_option.rb +22 -0
  34. data/lib/tabry/models/include_sub.rb +18 -0
  35. data/lib/tabry/models/option.rb +33 -0
  36. data/lib/tabry/models/option_base.rb +20 -0
  37. data/lib/tabry/models/option_includes.rb +14 -0
  38. data/lib/tabry/models/options_list.rb +18 -0
  39. data/lib/tabry/models/shell_option.rb +13 -0
  40. data/lib/tabry/models/sub.rb +59 -0
  41. data/lib/tabry/models/subs_list.rb +28 -0
  42. data/lib/tabry/options_finder.rb +87 -0
  43. data/lib/tabry/result.rb +110 -0
  44. data/lib/tabry/runner.rb +27 -0
  45. data/lib/tabry/state.rb +14 -0
  46. data/lib/tabry/usage_generator.rb +137 -0
  47. data/lib/tabry/util.rb +15 -0
  48. data/sh/tabry_bash.sh +61 -0
  49. data/sh/tabry_bash_help.sh +7 -0
  50. data/spec/fixtures/basiccli.json +1 -0
  51. data/spec/fixtures/basiccli.tabry +5 -0
  52. data/spec/fixtures/basiccli2.tabry +5 -0
  53. data/spec/fixtures/basiccli2.yml +7 -0
  54. data/spec/fixtures/vehicles.tabry +60 -0
  55. data/spec/fixtures/vehicles.yaml +135 -0
  56. data/spec/spec_helper.rb +10 -0
  57. data/spec/tabry/cli/arg_proxy_spec.rb +76 -0
  58. data/spec/tabry/cli/builder_spec.rb +226 -0
  59. data/spec/tabry/config_loader_spec.rb +69 -0
  60. data/spec/tabry/machine_spec.rb +109 -0
  61. data/spec/tabry/models/args_list_spec.rb +36 -0
  62. data/spec/tabry/models/config_spec.rb +62 -0
  63. data/spec/tabry/models/const_option_spec.rb +17 -0
  64. data/spec/tabry/models/dir_option_spec.rb +16 -0
  65. data/spec/tabry/models/file_option_spec.rb +16 -0
  66. data/spec/tabry/models/options_list_spec.rb +47 -0
  67. data/spec/tabry/models/shell_option_spec.rb +19 -0
  68. data/spec/tabry/models/subs_list_spec.rb +24 -0
  69. data/spec/tabry/options_finder_spec.rb +91 -0
  70. data/spec/tabry/result_spec.rb +236 -0
  71. data/spec/tabry/runner_spec.rb +35 -0
  72. data/spec/tabry/usage_generator_spec.rb +67 -0
  73. data/tabry.gemspec +27 -0
  74. metadata +189 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8531aa56c6c59a5d678e280ccb39fa8fd891aadac1d4fd194d73cacee20aaa47
4
+ data.tar.gz: ab532e18804f52824ccebf21667aa8da6416324eaa428d58140b758e22160b6a
5
+ SHA512:
6
+ metadata.gz: 62d4481040a2c2e06e5f16e0c5ef559fddf4cb6330d61d9962af8295c940d56c40bce23df662dbb168c83c48ed929fe30e1f2bf7e91ddc6fda06595c9576b63c
7
+ data.tar.gz: 1447eb824ec57c40b4dcc4eaeaefbd1488f514bd10535c7035f3439558f03a1368c1cf1cc7674804d2976ab9dadede3d0a818ff9e87e140cd58f67e983026ffb
data/bin/tabry-bash ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "shellwords"
5
+ require "yaml"
6
+ require_relative "../lib/tabry/util"
7
+ require_relative "../lib/tabry/runner"
8
+
9
+ # Bash-specific entrypoint, taking COMP_WORDS and COMP_CWORDS and returning possible options
10
+
11
+ COMP_POINT_SENTINEL = "\uFFFF"
12
+
13
+ # TODO: in weird scenarios this acts weird: namely, special shell operators like <(ls), $$((1 + 1))
14
+ # Also it crashed on unbalanced quotes, like: foo "bar<TAB>
15
+ # however, this will handle the common scenarios of escaping with quotes, single quotes, and backslashes
16
+ # Split up args and put the argument that comp_point is in in the `last_arg` variable.
17
+ # Just cutting off everything after comp_point might have worked, although
18
+ # maybe we wanted the whole arg? Not sure this is the best.
19
+ cmd_line, comp_point = ARGV
20
+ cmd_line = cmd_line.dup
21
+ cmd_line[comp_point.to_i...comp_point.to_i] = COMP_POINT_SENTINEL
22
+ cmd, *all_args = Shellwords.split(cmd_line)
23
+ last_arg_index = all_args.index { |arg| arg.include?(COMP_POINT_SENTINEL) }
24
+ args = all_args[0..last_arg_index]
25
+ last_arg = args.pop.gsub! COMP_POINT_SENTINEL, ""
26
+
27
+ cmd_name = cmd.gsub(%r{.*/}, "")
28
+
29
+ opts = Tabry::Runner.new(config_name: cmd_name).options(args, last_arg)
30
+
31
+ if Tabry::Util.debug?
32
+ require "json"
33
+ $stderr.puts
34
+ warn "debug: got command line and comp_point: #{cmd_line.inspect}, #{comp_point}"
35
+ warn "using args: #{args.inspect}"
36
+ warn "using lastarg: #{last_arg.inspect}"
37
+ warn "results from Tabry#options(): #{opts.inspect}"
38
+ warn "--- end debug output ---"
39
+ end
40
+
41
+ normal_opts = opts.select { |t| t.is_a?(String) }
42
+ special_opts = opts.select { |t| t.is_a?(Symbol) }
43
+
44
+ puts normal_opts.map { |t| Shellwords.escape(t) }.join("\n")
45
+ if special_opts.any?
46
+ puts
47
+ puts special_opts.join("\n")
48
+ end
data/bin/tabry-help ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/tabry/runner"
5
+
6
+ # Show usage information stored in a tabry configuration.
7
+ #
8
+ # For ease of use, source sh/tabry_bash_help.sh so you can just do
9
+ # "help mycmd"
10
+
11
+ raise "Usage: tabry-help config_name [arg1 [arg2]]..." unless ARGV.length >= 1
12
+
13
+ config_name, *args = ARGV
14
+ result = Tabry::Runner.new(config_name: config_name).parse(args)
15
+ usage = result.usage(config_name)
16
+ puts "(Usage info from tabry-help; running command directly may provide more help)\n\n"
17
+ puts usage
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/tabry/runner"
5
+
6
+ raise "Usage: tabry-test-options config_name arg1 [arg2]..." unless ARGV.length >= 1
7
+
8
+ config_name, *args, last = ARGV
9
+ puts Tabry::Runner.new(config_name: config_name).options(args, last).inspect
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/tabry/runner"
5
+
6
+ # Test parsing of arguments for a config
7
+
8
+ raise "Usage: tabry-test-parse config_name arg1 [arg2]..." unless ARGV.length >= 1
9
+
10
+ config_name, *args = ARGV
11
+ runner = Tabry::Runner.new(config_name: config_name)
12
+ result = runner.parse(args)
13
+
14
+ puts result.state
15
+ if result.invalid_usage_reason
16
+ puts "Invalid usage: #{result.invalid_usage_reason}"
17
+ else
18
+ puts "Valid usage"
19
+ end
20
+ puts "Named args: #{result.named_args.inspect}"
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "flag_proxy"
4
+
5
+ module Tabry
6
+ module CLI
7
+ class ArgProxy
8
+ include Enumerable
9
+
10
+ def initialize(args, named_args, reqd: false)
11
+ @args = args
12
+ @named_args = FlagProxy.new(named_args)
13
+ @raw_named_args = named_args
14
+ @reqd = reqd
15
+ @reqd_arg_proxy = ArgProxy.new(args, named_args, reqd: true) unless @reqd
16
+ end
17
+
18
+ def each(...)
19
+ @args.each(...)
20
+ end
21
+
22
+ def [](key)
23
+ if key.is_a?(Integer)
24
+ res = @args[key]
25
+ if @reqd && !res
26
+ warn "FATAL: Missing required argument number #{key + 1}"
27
+ exit 1
28
+ end
29
+ else
30
+ key = key.to_s
31
+ res = @named_args[key]
32
+ if @reqd && !res
33
+ warn "FATAL: Missing required argument #{key}"
34
+ exit 1
35
+ end
36
+ end
37
+ res
38
+ end
39
+
40
+ def slice(*keys)
41
+ [keys].flatten.each_with_object({}) do |key, result_hash|
42
+ result_hash[key] = self[key]
43
+ end
44
+ end
45
+
46
+ def <=>(*args)
47
+ @args.<=>(*args)
48
+ end
49
+
50
+ def method_missing(met)
51
+ self[met]
52
+ end
53
+
54
+ def respond_to_missing?(*_args)
55
+ true # Anything could be an arg name that we haven't set
56
+ end
57
+
58
+ def inspect
59
+ "ArgProxy: #{@args.inspect}, #{@raw_named_args.inspect}"
60
+ end
61
+
62
+ def to_s
63
+ "ArgProxy: #{@args}, #{@raw_named_args.inspect}"
64
+ end
65
+
66
+ def reqd
67
+ @reqd_arg_proxy or raise "no reqd.reqd"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "arg_proxy"
4
+ require_relative "flag_proxy"
5
+
6
+ module Tabry
7
+ module CLI
8
+ class Base
9
+ attr_reader :args, :flags, :internals
10
+
11
+ def initialize(flags, args, named_args, internals)
12
+ @args = ArgProxy.new(args, named_args)
13
+ @flags = FlagProxy.new(flags)
14
+ @internals = internals
15
+ end
16
+
17
+ def self.sub_route(prefix, cli_class)
18
+ (@sub_route_clis ||= {})[prefix.to_s] = cli_class
19
+ end
20
+
21
+ def self.after_action(*method_names, only: nil, except: nil, &blk)
22
+ [*method_names, blk].compact.each do |met|
23
+ (@after_actions ||= []) << [met, { only: only, except: except }]
24
+ end
25
+ end
26
+
27
+ def self.before_action(*method_names, only: nil, except: nil, &blk)
28
+ [*method_names, blk].compact.each do |met|
29
+ (@before_actions ||= []) << [met, { only: only, except: except }]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../runner"
4
+ require_relative "../util"
5
+ require_relative "internals"
6
+
7
+ module Tabry
8
+ module CLI
9
+ class Builder
10
+ attr_reader :config, :cli_class, :runner
11
+
12
+ def initialize(config_name, cli_class)
13
+ @cli_class = cli_class
14
+ @runner = Tabry::Runner.new(config_name: config_name)
15
+ end
16
+
17
+ DISALLOWED_SUBCOMMAND_NAMES = %w[args flags internals].freeze
18
+
19
+ def run(raw_args)
20
+ result = runner.parse(raw_args)
21
+ check_for_correct_usage(result)
22
+
23
+ state = result.state
24
+
25
+ met = state.subcommand_stack.join("__").gsub("-", "_")
26
+
27
+ ::Tabry::Util.debug "met: #{met.inspect}"
28
+ ::Tabry::Util.debug "named_args: #{result.named_args.inspect}"
29
+
30
+ internals = Internals.new(
31
+ runner: runner, config: config, raw_args: raw_args,
32
+ state: state, met: met, result: result
33
+ )
34
+
35
+ cli = instantiate_cli(@cli_class, internals)
36
+ actual_cli, actual_met = get_cli_object_and_met(cli, met, internals)
37
+
38
+ cli_send_met(actual_cli, actual_met)
39
+ end
40
+
41
+ private
42
+
43
+ def check_for_correct_usage(result)
44
+ if result.help?
45
+ puts result.usage(File.basename($0))
46
+ exit 0
47
+ elsif result.invalid_usage_reason
48
+ puts "Invalid usage: #{result.invalid_usage_reason}"
49
+ puts
50
+ puts result.usage(File.basename($0))
51
+ exit(1)
52
+ end
53
+ end
54
+
55
+ def instantiate_cli(klass, internals)
56
+ return klass unless klass.is_a?(Class)
57
+
58
+ state = internals.state
59
+ klass.new(state.flags, state.args, internals.result.named_args, internals)
60
+ end
61
+
62
+ # Recursively look through sub_routes for the CLI to be used
63
+ def get_cli_object_and_met(cli, met, internals)
64
+ if DISALLOWED_SUBCOMMAND_NAMES.include?(met)
65
+ warn %(FATAL: Tabry does not support top-level subcommands named: #{DISALLOWED_SUBCOMMAND_NAMES.join(",")})
66
+ exit 1
67
+ end
68
+
69
+ return [cli, "main"] if met.to_s == ""
70
+
71
+ sub_route_clis = cli.class.instance_variable_get(:@sub_route_clis)
72
+ sub_name, rest = met.split("__", 2)
73
+
74
+ if sub_route_clis&.dig(sub_name)
75
+ sub_route_clis[sub_name] = instantiate_cli(sub_route_clis[sub_name], internals)
76
+ get_cli_object_and_met(sub_route_clis[sub_name], rest, internals)
77
+ else
78
+ [cli, met]
79
+ end
80
+ end
81
+
82
+ def cli_send_met(cli, met)
83
+ if cli.respond_to?(met.to_sym)
84
+ run_hooks(cli, met, :@before_actions)
85
+ cli.send(met.to_sym)
86
+ run_hooks(cli, met, :@after_actions)
87
+ else
88
+ warn %(FATAL: CLI does not support command #{met})
89
+ exit 1
90
+ end
91
+ end
92
+
93
+ def run_hooks(cli, met, instance_var)
94
+ met = met.to_s
95
+ cli.class.instance_variable_get(instance_var)&.each do |hook, opts|
96
+ next if Array(opts&.dig(:except))&.map(&:to_s)&.include?(met.to_s)
97
+ next if opts&.dig(:only) && !Array(opts[:only])&.map(&:to_s)&.include?(met.to_s)
98
+
99
+ hook.is_a?(Proc) ? cli.instance_eval(&hook) : cli.send(hook.to_sym)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tabry
4
+ module CLI
5
+ class FlagProxy
6
+ def initialize(hash)
7
+ @hash = hash
8
+ end
9
+
10
+ def [](key)
11
+ key = key.to_s
12
+ [
13
+ key,
14
+ key.gsub("_", "-")
15
+ ].each do |hash_key|
16
+ val = @hash[hash_key]
17
+ return val if val
18
+ end
19
+ nil
20
+ end
21
+
22
+ def slice(*keys)
23
+ [keys].flatten.each_with_object({}) do |key, result_hash|
24
+ result_hash[key] = self[key]
25
+ end
26
+ end
27
+
28
+ def method_missing(met, default = nil, *_args)
29
+ self[met] || default
30
+ end
31
+
32
+ def respond_to_missing?(*_args)
33
+ true
34
+ end
35
+
36
+ def inspect
37
+ "FlagProxy: #{@hash.inspect}"
38
+ end
39
+
40
+ def to_s
41
+ "FlagProxy: #{@hash}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tabry
4
+ module CLI
5
+ Internals = Struct.new(
6
+ :runner, :config_name, :config, :raw_args, :state, :met, :result,
7
+ keyword_init: true
8
+ )
9
+ end
10
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+ require "yaml"
5
+ require "json"
6
+
7
+ # Simple YAML configuration
8
+ # Example:
9
+ # require 'tabry/cli/util/config'
10
+ # module MyCli
11
+ # Config = Tabry::CLI::Util::Config.new('~/.mycli.yml')
12
+ # end
13
+
14
+ class Hash
15
+ def to_openstruct
16
+ JSON.parse to_json, object_class: OpenStruct
17
+ end
18
+ end
19
+
20
+ class Array
21
+ def to_openstruct
22
+ JSON.parse to_json, object_class: OpenStruct
23
+ end
24
+ end
25
+
26
+ module Tabry
27
+ module CLI
28
+ module Util
29
+ class Config
30
+ def initialize(path)
31
+ @path = path.gsub(/^~/, Dir.home)
32
+ end
33
+
34
+ def config
35
+ @config ||= YAML.load_file(@path).to_openstruct
36
+ end
37
+
38
+ def method_missing(*args)
39
+ config.send(*args)
40
+ end
41
+
42
+ def respond_to_missing?(*args)
43
+ super?(*args) || config.respond_to_missing?(*args)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ # Utils that may be useful to CLIs
6
+ module Tabry
7
+ module CLI
8
+ module Util
9
+ module_function
10
+
11
+ def make_cmdline(cmdline, *args, echo: false, echo_only: false)
12
+ args = Array(args).flatten
13
+ cmdline = cmdline % args.map { |a| Shellwords.escape(a) }
14
+ warn cmdline if echo || echo_only
15
+ return nil if echo_only
16
+
17
+ cmdline
18
+ end
19
+
20
+ def system(*cmdline, **opts)
21
+ cmdline = make_cmdline(*cmdline, **opts)
22
+ Kernel.system cmdline if cmdline
23
+ end
24
+
25
+ # TODO: would be nice to get separate STDERR and STDOUT
26
+ def backtick_or_die(*cmdline, **opts)
27
+ backtick_with_process_status(*cmdline, **opts).first
28
+ end
29
+
30
+ def backtick_with_process_status(*cmdline, valid_statuses: [0], **opts)
31
+ cmdline = make_cmdline(*cmdline, **opts)
32
+ return [nil, nil] unless cmdline
33
+
34
+ res = `#{cmdline}`
35
+ status = $?
36
+ if valid_statuses && !valid_statuses.include?(status.exitstatus)
37
+ warn "COMMAND FAILED with exit code #{status.exitstatus}: #{cmdline}"
38
+ exit 1
39
+ end
40
+ [res, status]
41
+ end
42
+
43
+ def open_web_page(url)
44
+ command = RUBY_PLATFORM.include?("linux") ? "xdg-open" : "open"
45
+ unless system("(%s %s 2>&1) >/dev/null", command, url)
46
+ warn "WARNING: opening web page failed: #{make_cmdline("%s %s", command, url)}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "models/config"
4
+
5
+ module Tabry
6
+ class ConfigLoader
7
+ class ConfigNotFound < StandardError; end
8
+
9
+ EXTENSIONS = %w[json yml yaml].freeze
10
+
11
+ def self.load(**args)
12
+ new(**args).load
13
+ end
14
+
15
+ attr_reader :name
16
+
17
+ def initialize(name:)
18
+ @name = name
19
+ end
20
+
21
+ def load
22
+ return load_from_file(name) if name =~ /\.json$/i || name =~ /\.ya?ml$/i
23
+
24
+ load_paths.each do |dir|
25
+ EXTENSIONS.each do |extension|
26
+ filename = "#{dir}/#{name}.#{extension}"
27
+ return load_from_file(filename) if File.exist?(filename)
28
+ end
29
+ end
30
+
31
+ raise ConfigNotFound, "Could not find Tabry config #{name}.(#{EXTENSIONS.join(", ")}) in paths: #{load_paths.inspect}"
32
+ end
33
+
34
+ private
35
+
36
+ def load_paths
37
+ @load_paths ||= [
38
+ *ENV["TABRY_IMPORTS_PATH"]&.split(":")&.reject(&:empty?),
39
+ Dir.home + "/.tabry/",
40
+ ]
41
+ end
42
+
43
+ def load_from_file(filename)
44
+ if filename =~ /\.json$/
45
+ require "json"
46
+ Tabry::Models::Config.new(raw: JSON.parse(File.read(filename)))
47
+ elsif filename =~ /\.ya?ml$/
48
+ require "yaml"
49
+ Tabry::Models::Config.new(raw: YAML.safe_load(File.read(filename)))
50
+ else
51
+ raise "unknown file type"
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ class String
4
+ def decolorize
5
+ gsub(/\e\[(\d+)(;\d+)*m/, "")
6
+ end
7
+
8
+ def black
9
+ "\e[30m#{self}\e[0m"
10
+ end
11
+
12
+ def red
13
+ "\e[31m#{self}\e[0m"
14
+ end
15
+
16
+ def green
17
+ "\e[32m#{self}\e[0m"
18
+ end
19
+
20
+ def brown
21
+ "\e[33m#{self}\e[0m"
22
+ end
23
+
24
+ def blue
25
+ "\e[34m#{self}\e[0m"
26
+ end
27
+
28
+ def magenta
29
+ "\e[35m#{self}\e[0m"
30
+ end
31
+
32
+ def cyan
33
+ "\e[36m#{self}\e[0m"
34
+ end
35
+
36
+ def gray
37
+ "\e[37m#{self}\e[0m"
38
+ end
39
+
40
+ def bg_black
41
+ "\e[40m#{self}\e[0m"
42
+ end
43
+
44
+ def bg_red
45
+ "\e[41m#{self}\e[0m"
46
+ end
47
+
48
+ def bg_green
49
+ "\e[42m#{self}\e[0m"
50
+ end
51
+
52
+ def bg_brown
53
+ "\e[43m#{self}\e[0m"
54
+ end
55
+
56
+ def bg_blue
57
+ "\e[44m#{self}\e[0m"
58
+ end
59
+
60
+ def bg_magenta
61
+ "\e[45m#{self}\e[0m"
62
+ end
63
+
64
+ def bg_cyan
65
+ "\e[46m#{self}\e[0m"
66
+ end
67
+
68
+ def bg_gray
69
+ "\e[47m#{self}\e[0m"
70
+ end
71
+
72
+ def bold
73
+ "\e[1m#{self}\e[22m"
74
+ end
75
+
76
+ def italic
77
+ "\e[3m#{self}\e[23m"
78
+ end
79
+
80
+ def underline
81
+ "\e[4m#{self}\e[24m"
82
+ end
83
+
84
+ def blink
85
+ "\e[5m#{self}\e[25m"
86
+ end
87
+
88
+ def reverse_color
89
+ "\e[7m#{self}\e[27m"
90
+ end
91
+ end