cmdx 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 198e34bfa10cf9b640e3a768e335e3901063e2cde3817be7c63fb710b322acc6
4
- data.tar.gz: 59f99b87b1c2cc5fff64e2092f3f5ef5230a43d11de6f27c6609802627509164
3
+ metadata.gz: da7809a9d556cc2cb4cc5734fe52790f4e964ccc437728e1c78cef2a6e2121ec
4
+ data.tar.gz: 9577047cd8a98bc19fe81c02366aebf70c295e738e3083c478ab670c8af55048
5
5
  SHA512:
6
- metadata.gz: e60556e4f9af0a9af56d748a1084d95e5fecbe5316268d1d2f1414e5d4c323be81a7c56066bf19edf121eab3fb48080d6b5b3470086ee96241008eb28df9a152
7
- data.tar.gz: cc37a5a0029f708f624e449b0526060e203b649d282f33f69551c641ae06f34a3d53886b346b8e48b68f6dbaff23654171d34e58e7d854429c58f0c3d90c62af
6
+ metadata.gz: 3101e89eafcaf552fa64db81fdc0547628e23636564097ae91fb5dc726cbe25bcfbb6c44a04619cbbfcfe41f29f9a42a658bcd045a1a035f85b0c098d20a6038
7
+ data.tar.gz: 97450b9d1018dfc80d73fac8b0072356c4a1e05f39fc377cbf517468a412c77041bb1376ae0b3a72fb34575709fa169115ebf69ae7285a75fe41d453b21ee247
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-03-14
4
+ ### Added
5
+ - Add `progname` to logger instances
6
+ - Add `LoggerSerializer` to standardize log output
7
+ ### Changed
8
+ - Revert default log formatter to `Line`
9
+ - Removed `pid` from result serializer
10
+ - Fix serialization of frozen run
11
+ - Fix `call!` not marking state of failure as interrupted
12
+
3
13
  ## [0.2.0] - 2025-03-12
4
14
  ### Added
5
15
  - Add `PrettyJson` log formatter
data/docs/logging.md CHANGED
@@ -11,27 +11,27 @@ Built-in log formatters are:
11
11
 
12
12
  #### Success:
13
13
  ```txt
14
- I, [2022-07-17T18:43:15.000000 #3784] INFO -- CMDx: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=complete status=success outcome=success metadata={} runtime=0 tags=[] pid=3784
14
+ I, [2022-07-17T18:43:15.000000 #3784] INFO -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task task=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=complete status=success outcome=success metadata={} runtime=0 origin=CMDx
15
15
  ```
16
16
 
17
17
  #### Skipped:
18
18
  ```txt
19
- W, [2022-07-17T18:43:15.000000 #3784] WARN -- CMDx: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=interrupted status=skipped outcome=skipped metadata={} runtime=0 tags=[] pid=3784
19
+ W, [2022-07-17T18:43:15.000000 #3784] WARN -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task task=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=skipped outcome=skipped metadata={} runtime=0 origin=CMDx
20
20
  ```
21
21
 
22
22
  #### Failed:
23
23
  ```txt
24
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- CMDx: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=interrupted status=failed outcome=failed metadata={} runtime=0 tags=[] pid=3784
24
+ E, [2022-07-17T18:43:15.000000 #3784] ERROR -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task task=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=failed metadata={} runtime=0 origin=CMDx
25
25
  ```
26
26
 
27
27
  #### Level 1 subtask failure:
28
28
  ```txt
29
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- CMDx: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=interrupted status=failed outcome=interrupted metadata={} runtime=0 tags=[] pid=3784 caused_failure={:index=>1, :run_id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :type=>"Task", :class=>"SimulationTask", :id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :state=>"interrupted", :status=>"failed", :outcome=>"failed", :metadata=>{}, :runtime=>0, :tags=>[], :pid=>3784} threw_failure={:index=>1, :run_id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :type=>"Task", :class=>"SimulationTask", :id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :state=>"interrupted", :status=>"failed", :outcome=>"failed", :metadata=>{}, :runtime=>0, :tags=>[], :pid=>3784}
29
+ E, [2022-07-17T18:43:15.000000 #3784] ERROR -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task task=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=interrupted metadata={} runtime=0 caused_failure={index: 1, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", task: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "failed", metadata: {}, runtime: 0} threw_failure={index: 1, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", task: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "failed", metadata: {}, runtime: 0} origin=CMDx
30
30
  ```
31
31
 
32
32
  #### Level 2+ subtask failure:
33
33
  ```txt
34
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- CMDx: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 state=interrupted status=failed outcome=interrupted metadata={} runtime=0 tags=[] pid=3784 caused_failure={:index=>2, :run_id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :type=>"Task", :class=>"SimulationTask", :id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :state=>"interrupted", :status=>"failed", :outcome=>"failed", :metadata=>{}, :runtime=>0, :tags=>[], :pid=>3784} threw_failure={:index=>1, :run_id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :type=>"Task", :class=>"SimulationTask", :id=>"018c2b95-b764-7615-a924-cc5b910ed1e5", :state=>"interrupted", :status=>"failed", :outcome=>"interrupted", :metadata=>{}, :runtime=>0, :tags=>[], :pid=>3784}
34
+ E, [2022-07-17T18:43:15.000000 #3784] ERROR -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task task=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=interrupted metadata={} runtime=0 caused_failure={index: 2, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", task: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "failed", metadata: {}, runtime: 0} threw_failure={index: 1, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", task: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "interrupted", metadata: {}, runtime: 0} origin=CMDx
35
35
  ```
36
36
 
37
37
  ## Logger
@@ -92,7 +92,7 @@ Define a custom log formatter to match your expected output, for example one tha
92
92
 
93
93
  ```ruby
94
94
  class CustomCmdxLogFormat
95
- def call(severity, time, progname, message)
95
+ def call(severity, time, task, message)
96
96
  # Return string, hash, array, etc to output...
97
97
  end
98
98
  end
@@ -14,7 +14,7 @@ module CMDx
14
14
 
15
15
  def reset_configuration!
16
16
  @configuration = LazyStruct.new(
17
- logger: ::Logger.new($stdout, formatter: CMDx::LogFormatters::PrettyLine.new),
17
+ logger: ::Logger.new($stdout, formatter: CMDx::LogFormatters::Json.new),
18
18
  task_halt: CMDx::Result::FAILED,
19
19
  task_timeout: nil,
20
20
  batch_halt: CMDx::Result::FAILED,
@@ -4,9 +4,14 @@ module CMDx
4
4
  module LogFormatters
5
5
  class Json
6
6
 
7
- def call(_severity, _time, _progname, message)
8
- message = message.to_h if message.is_a?(Result)
9
- JSON.dump(message) << "\n"
7
+ def call(severity, time, task, message)
8
+ m = LoggerSerializer.call(severity, time, task, message).merge!(
9
+ severity:,
10
+ pid: Process.pid,
11
+ timestamp: Utils::LogTimestamp.call(time.utc)
12
+ )
13
+
14
+ JSON.dump(m) << "\n"
10
15
  end
11
16
 
12
17
  end
@@ -4,14 +4,15 @@ module CMDx
4
4
  module LogFormatters
5
5
  class KeyValue
6
6
 
7
- def call(_severity, _time, _progname, message)
8
- if message.is_a?(Result)
9
- message = message.to_h.map do |k, v|
10
- "#{k}=#{v}"
11
- end.join(" ")
12
- end
7
+ def call(severity, time, task, message)
8
+ m = LoggerSerializer.call(severity, time, task, message).merge!(
9
+ severity:,
10
+ pid: Process.pid,
11
+ timestamp: Utils::LogTimestamp.call(time.utc)
12
+ )
13
13
 
14
- message << "\n"
14
+ m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
15
+ m << "\n"
15
16
  end
16
17
 
17
18
  end
@@ -4,11 +4,12 @@ module CMDx
4
4
  module LogFormatters
5
5
  class Line
6
6
 
7
- def call(severity, time, progname, message)
8
- timestamp = Utils::LogTimestamp.call(time.utc)
9
- message = KeyValue.new.call(severity, time, progname, message).chomp
7
+ def call(severity, time, task, message)
8
+ t = Utils::LogTimestamp.call(time.utc)
9
+ m = LoggerSerializer.call(severity, time, task, message)
10
+ m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
10
11
 
11
- "#{severity[0]}, [#{timestamp} ##{Process.pid}] #{severity} -- #{progname || 'CMDx'}: #{message}\n"
12
+ "#{severity[0]}, [#{t} ##{Process.pid}] #{severity} -- #{task.class.name}: #{m}\n"
12
13
  end
13
14
 
14
15
  end
@@ -4,15 +4,15 @@ module CMDx
4
4
  module LogFormatters
5
5
  class Logstash
6
6
 
7
- def call(_severity, time, _progname, message)
8
- message = message.to_h if message.is_a?(Result)
7
+ def call(severity, time, task, message)
8
+ m = LoggerSerializer.call(severity, time, task, message).merge!(
9
+ severity:,
10
+ pid: Process.pid,
11
+ "@version" => "1",
12
+ "@timestamp" => Utils::LogTimestamp.call(time.utc)
13
+ )
9
14
 
10
- if message.is_a?(Hash)
11
- message["@version"] ||= "1"
12
- message["@timestamp"] ||= Utils::LogTimestamp.call(time.utc)
13
- end
14
-
15
- JSON.dump(message) << "\n"
15
+ JSON.dump(m) << "\n"
16
16
  end
17
17
 
18
18
  end
@@ -4,9 +4,14 @@ module CMDx
4
4
  module LogFormatters
5
5
  class PrettyJson
6
6
 
7
- def call(_severity, _time, _progname, message)
8
- message = message.to_h if message.is_a?(Result)
9
- JSON.pretty_generate(message) << "\n"
7
+ def call(severity, time, task, message)
8
+ m = LoggerSerializer.call(severity, time, task, message).merge!(
9
+ severity:,
10
+ pid: Process.pid,
11
+ timestamp: Utils::LogTimestamp.call(time.utc)
12
+ )
13
+
14
+ JSON.pretty_generate(m) << "\n"
10
15
  end
11
16
 
12
17
  end
@@ -4,19 +4,15 @@ module CMDx
4
4
  module LogFormatters
5
5
  class PrettyKeyValue
6
6
 
7
- COLORED_KEYS = %i[
8
- state status outcome
9
- ].freeze
7
+ def call(severity, time, task, message)
8
+ m = LoggerSerializer.call(severity, time, task, message, ansi_colorize: true).merge!(
9
+ severity:,
10
+ pid: Process.pid,
11
+ timestamp: Utils::LogTimestamp.call(time.utc)
12
+ )
10
13
 
11
- def call(_severity, _time, _progname, message)
12
- if message.is_a?(Result)
13
- message = message.to_h.map do |k, v|
14
- v = ResultAnsi.call(v) if COLORED_KEYS.include?(k)
15
- "#{k}=#{v}"
16
- end.join(" ")
17
- end
18
-
19
- message << "\n"
14
+ m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
15
+ m << "\n"
20
16
  end
21
17
 
22
18
  end
@@ -4,13 +4,14 @@ module CMDx
4
4
  module LogFormatters
5
5
  class PrettyLine
6
6
 
7
- def call(severity, time, progname, message)
8
- indicator = LoggerAnsi.call(severity[0])
9
- severity = LoggerAnsi.call(severity)
10
- timestamp = Utils::LogTimestamp.call(time.utc)
11
- message = PrettyKeyValue.new.call(severity, time, progname, message).chomp
7
+ def call(severity, time, task, message)
8
+ i = LoggerAnsi.call(severity[0])
9
+ s = LoggerAnsi.call(severity)
10
+ t = Utils::LogTimestamp.call(time.utc)
11
+ m = LoggerSerializer.call(severity, time, task, message, ansi_colorize: true)
12
+ m = m.map { |k, v| "#{k}=#{v}" }.join(" ") if m.is_a?(Hash)
12
13
 
13
- "#{indicator}, [#{timestamp} ##{Process.pid}] #{severity} -- #{progname || 'CMDx'}: #{message}\n"
14
+ "#{i}, [#{t} ##{Process.pid}] #{s} -- #{task.class.name}: #{m}\n"
14
15
  end
15
16
 
16
17
  end
@@ -4,7 +4,7 @@ module CMDx
4
4
  module LogFormatters
5
5
  class Raw
6
6
 
7
- def call(_severity, _time, _progname, message)
7
+ def call(_severity, _time, _task, message)
8
8
  message.inspect << "\n"
9
9
  end
10
10
 
data/lib/cmdx/logger.rb CHANGED
@@ -9,6 +9,7 @@ module CMDx
9
9
  logger = task.task_setting(:logger)
10
10
  logger.formatter = task.task_setting(:log_formatter) if task.task_setting?(:log_formatter)
11
11
  logger.level = task.task_setting(:log_level) if task.task_setting?(:log_level)
12
+ logger.progname = task
12
13
  logger
13
14
  end
14
15
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module LoggerSerializer
5
+
6
+ COLORED_KEYS = %i[
7
+ state status outcome
8
+ ].freeze
9
+
10
+ module_function
11
+
12
+ def call(_severity, _time, task, message, **options)
13
+ m = message.respond_to?(:to_h) ? message.to_h : {}
14
+
15
+ if options.delete(:ansi_colorize) && message.is_a?(Result)
16
+ COLORED_KEYS.each { |k| m[k] = ResultAnsi.call(m[k]) if m.key?(k) }
17
+ elsif !message.is_a?(Result)
18
+ m.merge!(
19
+ TaskSerializer.call(task),
20
+ message: message
21
+ )
22
+ end
23
+
24
+ m[:origin] ||= "CMDx"
25
+ m
26
+ end
27
+
28
+ end
29
+ end
data/lib/cmdx/result.rb CHANGED
@@ -28,6 +28,10 @@ module CMDx
28
28
  define_method(:"#{s}?") { state == s }
29
29
  end
30
30
 
31
+ def executed!
32
+ success? ? complete! : interrupt!
33
+ end
34
+
31
35
  def executed?
32
36
  complete? || interrupted?
33
37
  end
@@ -4,7 +4,7 @@ module CMDx
4
4
  module ResultInspector
5
5
 
6
6
  ORDERED_KEYS = %i[
7
- class type index id state status outcome metadata
7
+ task type index id state status outcome metadata
8
8
  tags pid runtime caused_failure threw_failure
9
9
  ].freeze
10
10
 
@@ -17,10 +17,10 @@ module CMDx
17
17
  value = result[key]
18
18
 
19
19
  case key
20
- when :class
20
+ when :task
21
21
  "#{value}:"
22
22
  when :caused_failure, :threw_failure
23
- "#{key}=<[#{value[:index]}] #{value[:class]}: #{value[:id]}>"
23
+ "#{key}=<[#{value[:index]}] #{value[:task]}: #{value[:id]}>"
24
24
  else
25
25
  "#{key}=#{value}"
26
26
  end
@@ -13,20 +13,15 @@ module CMDx
13
13
  module_function
14
14
 
15
15
  def call(result)
16
- {
17
- index: result.index,
18
- run_id: result.run.id,
19
- type: result.task.is_a?(Batch) ? "Batch" : "Task",
20
- class: result.task.class.name,
21
- id: result.task.id,
22
- state: result.state,
23
- status: result.status,
24
- outcome: result.outcome,
25
- metadata: result.metadata,
26
- runtime: result.runtime,
27
- tags: result.task.task_setting(:tags),
28
- pid: Process.pid
29
- }.tap do |hash|
16
+ TaskSerializer.call(result.task).tap do |hash|
17
+ hash.merge!(
18
+ state: result.state,
19
+ status: result.status,
20
+ outcome: result.outcome,
21
+ metadata: result.metadata,
22
+ runtime: result.runtime
23
+ )
24
+
30
25
  if result.failed?
31
26
  STRIP_FAILURE.call(hash, result, :caused_failure)
32
27
  STRIP_FAILURE.call(hash, result, :threw_failure)
data/lib/cmdx/run.rb CHANGED
@@ -13,6 +13,11 @@ module CMDx
13
13
  @results = Array(attributes[:results])
14
14
  end
15
15
 
16
+ def freeze
17
+ first_result
18
+ super
19
+ end
20
+
16
21
  def to_h
17
22
  RunSerializer.call(self)
18
23
  end
data/lib/cmdx/task.rb CHANGED
@@ -103,7 +103,6 @@ module CMDx
103
103
  end
104
104
 
105
105
  def after_call
106
- result.send(result.success? ? :complete! : :interrupt!)
107
106
  TaskHook.call(self, :"on_#{result.status}")
108
107
  TaskHook.call(self, :"on_#{result.state}")
109
108
 
@@ -126,6 +125,7 @@ module CMDx
126
125
  rescue StandardError => e
127
126
  fail!(reason: "[#{e.class}] #{e.message}", original_exception: e)
128
127
  ensure
128
+ result.executed!
129
129
  after_call
130
130
  end
131
131
 
@@ -139,10 +139,12 @@ module CMDx
139
139
  rescue UndefinedCallError => e
140
140
  raise(e)
141
141
  rescue Fault => e
142
+ result.executed!
142
143
  raise(e) if Array(task_setting(:task_halt)).include?(e.result.status)
143
144
 
144
145
  after_call # HACK: treat as NO-OP
145
146
  else
147
+ result.executed!
146
148
  after_call # ELSE: treat as success
147
149
  end
148
150
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module TaskSerializer
5
+
6
+ module_function
7
+
8
+ def call(task)
9
+ {
10
+ index: task.result.index,
11
+ run_id: task.run.id,
12
+ type: task.is_a?(Batch) ? "Batch" : "Task",
13
+ task: task.class.name,
14
+ id: task.id,
15
+ tags: task.task_setting(:tags)
16
+ }
17
+ end
18
+
19
+ end
20
+ end
data/lib/cmdx/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module CMDx
4
4
 
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
 
7
7
  end
data/lib/cmdx.rb CHANGED
@@ -48,6 +48,7 @@ require_relative "cmdx/error"
48
48
  require_relative "cmdx/errors"
49
49
  require_relative "cmdx/fault"
50
50
  require_relative "cmdx/faults"
51
+ require_relative "cmdx/logger_serializer"
51
52
  require_relative "cmdx/logger_ansi"
52
53
  require_relative "cmdx/logger"
53
54
  require_relative "cmdx/lazy_struct"
@@ -71,6 +72,7 @@ require_relative "cmdx/result_ansi"
71
72
  require_relative "cmdx/result_logger"
72
73
  require_relative "cmdx/task"
73
74
  require_relative "cmdx/task_hook"
75
+ require_relative "cmdx/task_serializer"
74
76
  require_relative "cmdx/batch"
75
77
  require_relative "cmdx/immutator"
76
78
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cmdx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juan Gomez
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-13 00:00:00.000000000 Z
10
+ date: 2025-03-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -225,6 +225,7 @@ files:
225
225
  - lib/cmdx/log_formatters/raw.rb
226
226
  - lib/cmdx/logger.rb
227
227
  - lib/cmdx/logger_ansi.rb
228
+ - lib/cmdx/logger_serializer.rb
228
229
  - lib/cmdx/parameter.rb
229
230
  - lib/cmdx/parameter_inspector.rb
230
231
  - lib/cmdx/parameter_serializer.rb
@@ -244,6 +245,7 @@ files:
244
245
  - lib/cmdx/run_serializer.rb
245
246
  - lib/cmdx/task.rb
246
247
  - lib/cmdx/task_hook.rb
248
+ - lib/cmdx/task_serializer.rb
247
249
  - lib/cmdx/utils/log_timestamp.rb
248
250
  - lib/cmdx/utils/monotonic_runtime.rb
249
251
  - lib/cmdx/utils/name_affix.rb