tabry 0.1.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.
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