mux_tf 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 924f8b4cc0e90b4c636e3ffcc806c0e48036380ac81ee83b1dc21a437950e76e
4
- data.tar.gz: 3bea784dc0db5091efa2dcba48c653b812d618b88c8dc04b997b59cc8e810e0c
3
+ metadata.gz: f9a03af7a131fd34e58d5fb27d3c578154f49246634a684980e6ebc16aa28d08
4
+ data.tar.gz: d093d1604dabbeb1aed8ef5b1ebc3e33a0b5e3575602f7b3802796b2b51f7a7a
5
5
  SHA512:
6
- metadata.gz: 3dbd88ebf68ae0bb27116ce5b553a04668a746cfdd89ed1e646fc6f75961cf8c61d66f2a4ba46d0baaa17c6fdaa3b99704485632a5787a492037e75707e06b1a
7
- data.tar.gz: bd9bf482078f080477f1871b02c256ad06b7bcb4e323adb8ee35fe056b9f8fd34bec4993c6b1611fac161b09b80db11c3d36395fdb42bef0d174e3ec666f8745
6
+ metadata.gz: 7809b83e63a8aa17671ef6fbaac79a09cd7aa8216fdd1ccd390f2e32ee0b4ec0a85e4f6653d4c4bbbafcb99d47076413a9135f970f4d4c0f7f8608b2e8858f5f
7
+ data.tar.gz: c9dd73ed72236b568c7bbdc34ede9701e1d1db94403ccd2e84ee611cd58500e73810f50c9c0bdd6817c2ef7f8bd5d504fd945d86d340db95d9246e797376efb4
data/exe/tf_current CHANGED
@@ -11,6 +11,8 @@ begin
11
11
  rescue Interrupt
12
12
  warn "\nInterrupted"
13
13
  exit 1
14
+ rescue SystemExit => e
15
+ exit e.status
14
16
  rescue Exception => e # rubocop:disable Lint/RescueException
15
17
  warn e.full_message
16
18
  warn "<press enter>"
data/exe/tf_mux CHANGED
@@ -11,6 +11,8 @@ begin
11
11
  rescue Interrupt
12
12
  warn "\nInterrupted"
13
13
  exit 1
14
+ rescue SystemExit => e
15
+ exit e.status
14
16
  rescue Exception => e # rubocop:disable Lint/RescueException
15
17
  warn e.full_message
16
18
  warn "<press enter>"
data/exe/tf_plan_summary CHANGED
@@ -11,6 +11,8 @@ begin
11
11
  rescue Interrupt
12
12
  warn "\nInterrupted"
13
13
  exit 1
14
+ rescue SystemExit => e
15
+ exit e.status
14
16
  rescue Exception => e # rubocop:disable Lint/RescueException
15
17
  warn e.full_message
16
18
  warn "<press enter>"
data/lib/deps.rb CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  require "bundler/inline"
4
4
 
5
- gemfile do
5
+ dep_def = proc do
6
6
  gemspec(path: File.join(__dir__, ".."))
7
7
  end
8
+
9
+ begin
10
+ gemfile(&dep_def)
11
+ rescue Bundler::GemNotFound
12
+ gemfile(true) do
13
+ source "https://rubygems.org"
14
+ instance_exec(&dep_def)
15
+ end
16
+ end
@@ -4,13 +4,14 @@ require "bundler"
4
4
 
5
5
  module MuxTf
6
6
  module Cli
7
- module Current
7
+ module Current # rubocop:disable Metrics/ModuleLength
8
8
  extend TerraformHelpers
9
9
  extend PiotrbCliUtils::Util
10
10
  extend PiotrbCliUtils::CriCommandSupport
11
11
  extend PiotrbCliUtils::CmdLoop
12
+ include Coloring
12
13
 
13
- class << self
14
+ class << self # rubocop:disable Metrics/ClassLength
14
15
  def run(args)
15
16
  version_check
16
17
 
@@ -19,8 +20,21 @@ module MuxTf
19
20
  return
20
21
  end
21
22
 
23
+ unless args.empty?
24
+ root_cmd = build_root_cmd
25
+ valid_commands = root_cmd.subcommands.map(&:name)
26
+
27
+ if args[0] && valid_commands.include?(args[0])
28
+ stop_reason = catch(:stop) {
29
+ root_cmd.run(args, {}, hard_exit: true)
30
+ }
31
+ log pastel.red("Stopped: #{stop_reason}") if stop_reason
32
+ return
33
+ end
34
+ end
35
+
22
36
  folder_name = File.basename(Dir.getwd)
23
- log "Processing #{Paint[folder_name, :cyan]} ..."
37
+ log "Processing #{pastel.cyan(folder_name)} ..."
24
38
 
25
39
  ENV["TF_IN_AUTOMATION"] = "1"
26
40
  ENV["TF_INPUT"] = "0"
@@ -32,19 +46,16 @@ module MuxTf
32
46
  return launch_cmd_loop(:error) unless upgrade_status == :ok
33
47
  end
34
48
 
35
- plan_status, @plan_meta = create_plan(plan_filename)
49
+ plan_status = run_plan
36
50
 
37
51
  case plan_status
38
52
  when :ok
39
- log "no changes, exiting", depth: 1
53
+ log "exiting", depth: 1
40
54
  when :error
41
- log "something went wrong", depth: 1
42
55
  launch_cmd_loop(plan_status)
43
- when :changes
44
- log "Printing Plan Summary ...", depth: 1
45
- pretty_plan_summary(plan_filename)
56
+ when :changes # rubocop:disable Lint/DuplicateBranch
46
57
  launch_cmd_loop(plan_status)
47
- when :unknown
58
+ when :unknown # rubocop:disable Lint/DuplicateBranch
48
59
  launch_cmd_loop(plan_status)
49
60
  end
50
61
  end
@@ -58,38 +69,105 @@ module MuxTf
58
69
  def version_check
59
70
  return unless VersionCheck.has_updates?
60
71
 
61
- log Paint["=" * 80, :yellow]
62
- log "New version of #{Paint['mux_tf', :cyan]} is available!"
63
- log "You are currently on version: #{Paint[VersionCheck.current_gem_version, :yellow]}"
64
- log "Latest version found is: #{Paint[VersionCheck.latest_gem_version, :green]}"
65
- log "Run `#{Paint['gem install mux_tf', :green]}` to update!"
66
- log Paint["=" * 80, :yellow]
72
+ log pastel.yellow("=" * 80)
73
+ log "New version of #{pastel.cyan('mux_tf')} is available!"
74
+ log "You are currently on version: #{pastel.yellow(VersionCheck.current_gem_version)}"
75
+ log "Latest version found is: #{pastel.green(VersionCheck.latest_gem_version)}"
76
+ log "Run `#{pastel.green('gem install mux_tf')}` to update!"
77
+ log pastel.yellow("=" * 80)
67
78
  end
68
79
 
69
- def run_validate
70
- remedies = PlanFormatter.process_validation(validate)
71
- process_remedies(remedies)
80
+ # block is expected to return a touple, the first element is a list of remedies
81
+ # the rest are any additional results
82
+ def remedy_retry_helper(from:, level: 1, attempt: 0, &block)
83
+ catch(:abort) do
84
+ until attempt > 1
85
+ attempt += 1
86
+ remedies, *results = block.call
87
+ return results if remedies.empty?
88
+
89
+ remedy_status, _remedy_results = process_remedies(remedies, from: from, level: level)
90
+ return unless remedy_status
91
+ end
92
+ log "!! giving up because attempt: #{attempt}"
93
+ end
72
94
  end
73
95
 
74
- def process_remedies(remedies)
96
+ # returns boolean true if succeeded
97
+ def run_validate(level: 1)
98
+ remedy_retry_helper(from: :validate, level: level) do
99
+ validation_info = validate
100
+ PlanFormatter.print_validation_errors(validation_info)
101
+ remedies = PlanFormatter.process_validation(validation_info)
102
+ [remedies, validation_info]
103
+ end
104
+ end
105
+
106
+ def process_remedies(remedies, from: nil, level: 1, retry_count: 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
107
+ remedies = remedies.dup
108
+ remedy = nil
109
+ wrap_log = lambda do |msg, color: nil|
110
+ [
111
+ from ? pastel.cyan("#{from} -> ") : nil,
112
+ pastel.cyan(remedy ? "[remedy: #{remedy}]" : "[process remedies]"),
113
+ " ",
114
+ color ? pastel.decorate(msg, color) : msg,
115
+ " ",
116
+ level > 1 ? pastel.cyan("[lv #{level}]") : nil,
117
+ retry_count.positive? ? pastel.cyan("[try #{retry_count}]") : nil
118
+ ].compact.join
119
+ end
120
+ results = {}
121
+ if retry_count > 5
122
+ log wrap_log["giving up because retry_count: #{retry_count}", color: :yellow], depth: 1
123
+ log wrap_log["unprocessed remedies: #{remedies.to_a}", color: :red], depth: 1
124
+ return [false, results]
125
+ end
75
126
  if remedies.delete? :init
76
- log "Running terraform init ...", depth: 2
77
- remedies = PlanFormatter.init_status_to_remedies(*PlanFormatter.run_tf_init)
78
- if process_remedies(remedies)
79
- remedies = PlanFormatter.process_validation(validate)
80
- return false unless process_remedies(remedies)
81
- end
127
+ remedy = :init
128
+ log wrap_log["Running terraform init ..."], depth: 2
129
+ exit_code, meta = PlanFormatter.run_tf_init
130
+ print_errors_and_warnings(meta)
131
+ remedies = PlanFormatter.init_status_to_remedies(exit_code, meta)
132
+ status, r_results = process_remedies(remedies, from: from, level: level + 1)
133
+ results.merge!(r_results)
134
+ return [true, r_results] if status
135
+ end
136
+ if remedies.delete?(:plan)
137
+ remedy = :plan
138
+ log wrap_log["Running terraform plan ..."], depth: 2
139
+ plan_status = run_plan(retry_count: retry_count)
140
+ results[:plan_status] = plan_status
141
+ return [false, results] unless [:ok, :changes].include?(plan_status)
82
142
  end
83
143
  if remedies.delete? :reconfigure
84
- log "Running terraform init ...", depth: 2
85
- remedies = PlanFormatter.init_status_to_remedies(*PlanFormatter.run_tf_init(reconfigure: true))
86
- return false unless process_remedies(remedies)
144
+ remedy = :reconfigure
145
+ log wrap_log["Running terraform init ..."], depth: 2
146
+ result = remedy_retry_helper(from: :reconfigure, level: level + 1, attempt: retry_count) {
147
+ exit_code, meta = PlanFormatter.run_tf_init(reconfigure: true)
148
+ print_errors_and_warnings(meta)
149
+ remedies = PlanFormatter.init_status_to_remedies(exit_code, meta)
150
+ [remedies, exit_code, meta]
151
+ }
152
+ unless result
153
+ log wrap_log["Failed", color: :red], depth: 2
154
+ return [false, result]
155
+ end
156
+ end
157
+ if remedies.delete? :user_error
158
+ remedy = :user_error
159
+ log wrap_log["user error encountered!", color: :red]
160
+ log wrap_log["-" * 40, color: :red]
161
+ log wrap_log["!! User Error, Please fix the issue and try again", color: :red]
162
+ log wrap_log["-" * 40, color: :red]
163
+ return [false, results]
87
164
  end
88
165
  unless remedies.empty?
89
- log "unprocessed remedies: #{remedies.to_a}", depth: 1
90
- return false
166
+ remedy = nil
167
+ log wrap_log["Unprocessed remedies: #{remedies.to_a}", color: :red], depth: 1 if level == 1
168
+ return [false, results]
91
169
  end
92
- true
170
+ [true, results]
93
171
  end
94
172
 
95
173
  def validate
@@ -108,7 +186,7 @@ module MuxTf
108
186
  when 2
109
187
  [:changes, meta]
110
188
  else
111
- log Paint["terraform plan exited with an unknown exit code: #{exit_code}", :yellow]
189
+ log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
112
190
  [:unknown, meta]
113
191
  end
114
192
  end
@@ -118,9 +196,9 @@ module MuxTf
118
196
 
119
197
  case status
120
198
  when :error, :unknown
121
- log Paint["Dropping to command line so you can fix the issue!", :red]
199
+ log pastel.red("Dropping to command line so you can fix the issue!")
122
200
  when :changes
123
- log Paint["Dropping to command line so you can review the changes.", :yellow]
201
+ log pastel.yellow("Dropping to command line so you can review the changes.")
124
202
  end
125
203
  cmd_loop(status)
126
204
  end
@@ -135,9 +213,9 @@ module MuxTf
135
213
  prompt = "#{folder_name} => "
136
214
  case status
137
215
  when :error, :unknown
138
- prompt = "[#{Paint[status.to_s, :red]}] #{prompt}"
216
+ prompt = "[#{pastel.red(status.to_s)}] #{prompt}"
139
217
  when :changes
140
- prompt = "[#{Paint[status.to_s, :yellow]}] #{prompt}"
218
+ prompt = "[#{pastel.yellow(status.to_s)}] #{prompt}"
141
219
  end
142
220
 
143
221
  run_cmd_loop(prompt) do |cmd|
@@ -157,11 +235,31 @@ module MuxTf
157
235
  root_cmd.add_command(upgrade_cmd)
158
236
  root_cmd.add_command(reconfigure_cmd)
159
237
  root_cmd.add_command(interactive_cmd)
238
+ root_cmd.add_command(plan_details_cmd)
160
239
 
161
240
  root_cmd.add_command(exit_cmd)
241
+ root_cmd.add_command(define_cmd("help", summary: "Show help for commands") { |_opts, _args, cmd| puts cmd.supercommand.help })
162
242
  root_cmd
163
243
  end
164
244
 
245
+ def plan_summary_text
246
+ plan_filename = PlanFilenameGenerator.for_path
247
+ if File.exist?("#{plan_filename}.txt") && File.mtime("#{plan_filename}.txt").to_f >= File.mtime(plan_filename).to_f
248
+ File.read("#{plan_filename}.txt")
249
+ else
250
+ puts "Inspecting Changes ... #{plan_filename}"
251
+ data = PlanUtils.text_version_of_plan_show(plan_filename)
252
+ File.write("#{plan_filename}.txt", data)
253
+ data
254
+ end
255
+ end
256
+
257
+ def plan_details_cmd
258
+ define_cmd("details", summary: "Show Plan Details") do |_opts, _args, _cmd|
259
+ puts plan_summary_text
260
+ end
261
+ end
262
+
165
263
  def plan_cmd
166
264
  define_cmd("plan", summary: "Re-run plan") do |_opts, _args, _cmd|
167
265
  run_validate && run_plan
@@ -182,28 +280,30 @@ module MuxTf
182
280
 
183
281
  def shell_cmd
184
282
  define_cmd("shell", summary: "Open your default terminal in the current folder") do |_opts, _args, _cmd|
185
- log Paint["Launching shell ...", :yellow]
186
- log Paint["When it exits you will be back at this prompt.", :yellow]
283
+ log pastel.yellow("Launching shell ...")
284
+ log pastel.yellow("When it exits you will be back at this prompt.")
187
285
  system ENV.fetch("SHELL")
188
286
  end
189
287
  end
190
288
 
191
289
  def force_unlock_cmd
192
- define_cmd("force-unlock", summary: "Force unlock state after encountering a lock error!") do
290
+ define_cmd("force-unlock", summary: "Force unlock state after encountering a lock error!") do # rubocop:disable Metrics/BlockLength
193
291
  prompt = TTY::Prompt.new(interrupt: :noop)
194
292
 
195
- table = TTY::Table.new(header: %w[Field Value])
196
- table << ["Lock ID", @plan_meta["ID"]]
197
- table << ["Operation", @plan_meta["Operation"]]
198
- table << ["Who", @plan_meta["Who"]]
199
- table << ["Created", @plan_meta["Created"]]
293
+ lock_info = @last_lock_info
294
+
295
+ if lock_info
296
+ table = TTY::Table.new(header: %w[Field Value])
297
+ table << ["Lock ID", lock_info[:lock_id]]
298
+ table << ["Operation", lock_info[:operation]]
299
+ table << ["Who", lock_info[:who]]
300
+ table << ["Created", lock_info[:created]]
200
301
 
201
- puts table.render(:unicode, padding: [0, 1])
302
+ puts table.render(:unicode, padding: [0, 1])
202
303
 
203
- if @plan_meta && @plan_meta["error"] == "lock"
204
304
  done = catch(:abort) {
205
- if @plan_meta["Operation"] != "OperationTypePlan" && !prompt.yes?(
206
- "Are you sure you want to force unlock a lock for operation: #{@plan_meta['Operation']}",
305
+ if lock_info[:operation] != "OperationTypePlan" && !prompt.yes?(
306
+ "Are you sure you want to force unlock a lock for operation: #{lock_info[:operation]}",
207
307
  default: false
208
308
  )
209
309
  throw :abort
@@ -214,19 +314,19 @@ module MuxTf
214
314
  default: false
215
315
  )
216
316
 
217
- status = tf_force_unlock(id: @plan_meta["ID"])
317
+ status = tf_force_unlock(id: lock_info[:lock_id])
218
318
  if status.success?
219
319
  log "Done!"
220
320
  else
221
- log Paint["Failed with status: #{status}", :red]
321
+ log pastel.red("Failed with status: #{status}")
222
322
  end
223
323
 
224
324
  true
225
325
  }
226
326
 
227
- log Paint["Aborted", :yellow] unless done
327
+ log pastel.yellow("Aborted") unless done
228
328
  else
229
- log Paint["No lock error or no plan ran!", :red]
329
+ log pastel.red("No lock error or no plan ran!")
230
330
  end
231
331
  end
232
332
  end
@@ -243,8 +343,9 @@ module MuxTf
243
343
 
244
344
  def reconfigure_cmd
245
345
  define_cmd("reconfigure", summary: "Reconfigure modules/plguins") do |_opts, _args, _cmd|
246
- status, meta = PlanFormatter.run_tf_init(reconfigure: true)
247
- if status != 0
346
+ exit_code, meta = PlanFormatter.run_tf_init(reconfigure: true)
347
+ print_errors_and_warnings(meta)
348
+ if exit_code != 0
248
349
  log meta.inspect unless meta.empty?
249
350
  log "Reconfigure Failed!"
250
351
  end
@@ -257,7 +358,7 @@ module MuxTf
257
358
  begin
258
359
  abort_message = catch(:abort) { plan.run_interactive }
259
360
  if abort_message
260
- log Paint["Aborted: #{abort_message}", :red]
361
+ log pastel.red("Aborted: #{abort_message}")
261
362
  else
262
363
  run_plan
263
364
  end
@@ -268,8 +369,78 @@ module MuxTf
268
369
  end
269
370
  end
270
371
 
271
- def run_plan(targets: [])
272
- plan_status, @plan_meta = create_plan(plan_filename, targets: targets)
372
+ def print_errors_and_warnings(meta) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
373
+ message = []
374
+ message << pastel.yellow("#{meta[:warnings].length} Warnings") if meta[:warnings]
375
+ message << pastel.red("#{meta[:errors].length} Errors") if meta[:errors]
376
+ if message.length.positive?
377
+ log ""
378
+ log "Encountered: #{message.join(' and ')}"
379
+ log ""
380
+ end
381
+
382
+ meta[:warnings]&.each do |warning|
383
+ log "-" * 20
384
+ log pastel.yellow("Warning: #{warning[:message]}")
385
+ warning[:body]&.each do |line|
386
+ log pastel.yellow(line), depth: 1
387
+ end
388
+ log ""
389
+ end
390
+
391
+ meta[:errors]&.each do |error|
392
+ log "-" * 20
393
+ log pastel.red("Error: #{error[:message]}")
394
+ error[:body]&.each do |line|
395
+ log pastel.red(line), depth: 1
396
+ end
397
+ log ""
398
+ end
399
+
400
+ return unless message.length.positive?
401
+
402
+ log ""
403
+ end
404
+
405
+ def detect_remedies_from_plan(meta)
406
+ remedies = Set.new
407
+ meta[:errors]&.each do |error|
408
+ remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
409
+ end
410
+ remedies << :unlock if lock_error?(meta)
411
+ remedies
412
+ end
413
+
414
+ def lock_error?(meta)
415
+ meta && meta["error"] == "lock"
416
+ end
417
+
418
+ def extract_lock_info(meta)
419
+ {
420
+ lock_id: meta["ID"],
421
+ operation: meta["Operation"],
422
+ who: meta["Who"],
423
+ created: meta["Created"]
424
+ }
425
+ end
426
+
427
+ def run_plan(targets: [], level: 1, retry_count: 0)
428
+ plan_status, = remedy_retry_helper(from: :plan, level: level, attempt: retry_count) {
429
+ @last_lock_info = nil
430
+
431
+ plan_status, meta = create_plan(plan_filename, targets: targets)
432
+
433
+ print_errors_and_warnings(meta)
434
+
435
+ remedies = detect_remedies_from_plan(meta)
436
+
437
+ if remedies.include?(:unlock)
438
+ @last_lock_info = extract_lock_info(meta)
439
+ throw :abort, [plan_status, meta]
440
+ end
441
+
442
+ [remedies, plan_status, meta]
443
+ }
273
444
 
274
445
  case plan_status
275
446
  when :ok
@@ -277,25 +448,30 @@ module MuxTf
277
448
  when :error
278
449
  log "something went wrong", depth: 1
279
450
  when :changes
280
- log "Printing Plan Summary ...", depth: 1
281
- pretty_plan_summary(plan_filename)
451
+ unless ENV["JSON_PLAN"]
452
+ log "Printing Plan Summary ...", depth: 1
453
+ pretty_plan_summary(plan_filename)
454
+ end
455
+ puts plan_summary_text if ENV["JSON_PLAN"]
282
456
  when :unknown
283
457
  # nothing
284
458
  end
459
+
285
460
  plan_status
286
461
  end
287
462
 
463
+ public :run_plan
464
+
288
465
  def run_upgrade
289
466
  exit_code, meta = PlanFormatter.run_tf_init(upgrade: true)
467
+ print_errors_and_warnings(meta)
290
468
  case exit_code
291
469
  when 0
292
470
  [:ok, meta]
293
471
  when 1
294
472
  [:error, meta]
295
- # when 2
296
- # [:changes, meta]
297
473
  else
298
- log Paint["terraform init upgrade exited with an unknown exit code: #{exit_code}", :yellow]
474
+ log pastel.yellow("terraform init upgrade exited with an unknown exit code: #{exit_code}")
299
475
  [:unknown, meta]
300
476
  end
301
477
  end
@@ -6,6 +6,7 @@ module MuxTf
6
6
  extend PiotrbCliUtils::Util
7
7
  extend PiotrbCliUtils::ShellHelpers
8
8
  extend TerraformHelpers
9
+ import Coloring
9
10
 
10
11
  class << self
11
12
  def run(args)
@@ -33,7 +34,7 @@ module MuxTf
33
34
 
34
35
  if options[:interactive]
35
36
  abort_message = catch(:abort) { plan.run_interactive }
36
- log Paint["Aborted: #{abort_message}", :red] if abort_message
37
+ log pastel.red("Aborted: #{abort_message}") if abort_message
37
38
  else
38
39
  if options[:hierarchy]
39
40
  plan.nested_summary.each do |line|
data/lib/mux_tf/cli.rb CHANGED
@@ -2,16 +2,18 @@
2
2
 
3
3
  module MuxTf
4
4
  module Cli
5
+ extend PiotrbCliUtils::Util
6
+
5
7
  def self.run(mode, args)
6
8
  case mode
7
9
  when :mux
8
- require_relative "./cli/mux"
10
+ require_relative "cli/mux"
9
11
  MuxTf::Cli::Mux.run(args)
10
12
  when :current
11
- require_relative "./cli/current"
13
+ require_relative "cli/current"
12
14
  MuxTf::Cli::Current.run(args)
13
15
  when :plan_summary
14
- require_relative "./cli/plan_summary"
16
+ require_relative "cli/plan_summary"
15
17
  MuxTf::Cli::PlanSummary.run(args)
16
18
  else
17
19
  fail_with "unhandled mode: #{mode.inspect}"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuxTf
4
+ module Coloring
5
+ def pastel
6
+ self.class.pastel
7
+ end
8
+
9
+ def self.included(other)
10
+ other.extend(ClassMethods)
11
+ end
12
+
13
+ module ClassMethods
14
+ def pastel
15
+ instance = Pastel.new
16
+ instance.alias_color(:orange, :yellow)
17
+ instance.alias_color(:gray, :bright_black)
18
+ instance.alias_color(:grey, :bright_black)
19
+ instance
20
+ end
21
+ end
22
+ end
23
+ end