cmdx 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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +64 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +132 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +76 -0
  10. data/Rakefile +12 -0
  11. data/docs/basics/call.md +31 -0
  12. data/docs/basics/context.md +67 -0
  13. data/docs/basics/run.md +34 -0
  14. data/docs/basics/setup.md +32 -0
  15. data/docs/batch.md +53 -0
  16. data/docs/configuration.md +57 -0
  17. data/docs/example.md +82 -0
  18. data/docs/getting_started.md +47 -0
  19. data/docs/hooks.md +59 -0
  20. data/docs/interruptions/exceptions.md +29 -0
  21. data/docs/interruptions/faults.md +89 -0
  22. data/docs/interruptions/halt.md +80 -0
  23. data/docs/logging.md +102 -0
  24. data/docs/outcomes/result.md +17 -0
  25. data/docs/outcomes/states.md +31 -0
  26. data/docs/outcomes/statuses.md +33 -0
  27. data/docs/parameters/coercions.md +52 -0
  28. data/docs/parameters/defaults.md +47 -0
  29. data/docs/parameters/definitions.md +123 -0
  30. data/docs/parameters/namespacing.md +57 -0
  31. data/docs/parameters/validations.md +312 -0
  32. data/docs/tips_and_tricks.md +79 -0
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/batch.rb +43 -0
  35. data/lib/cmdx/coercions/array.rb +15 -0
  36. data/lib/cmdx/coercions/big_decimal.rb +23 -0
  37. data/lib/cmdx/coercions/boolean.rb +27 -0
  38. data/lib/cmdx/coercions/complex.rb +21 -0
  39. data/lib/cmdx/coercions/date.rb +26 -0
  40. data/lib/cmdx/coercions/date_time.rb +26 -0
  41. data/lib/cmdx/coercions/float.rb +21 -0
  42. data/lib/cmdx/coercions/hash.rb +31 -0
  43. data/lib/cmdx/coercions/integer.rb +21 -0
  44. data/lib/cmdx/coercions/rational.rb +21 -0
  45. data/lib/cmdx/coercions/string.rb +15 -0
  46. data/lib/cmdx/coercions/time.rb +26 -0
  47. data/lib/cmdx/coercions/virtual.rb +15 -0
  48. data/lib/cmdx/configuration.rb +25 -0
  49. data/lib/cmdx/context.rb +15 -0
  50. data/lib/cmdx/core_ext/hash.rb +36 -0
  51. data/lib/cmdx/core_ext/module.rb +48 -0
  52. data/lib/cmdx/core_ext/object.rb +55 -0
  53. data/lib/cmdx/error.rb +23 -0
  54. data/lib/cmdx/errors.rb +92 -0
  55. data/lib/cmdx/fault.rb +47 -0
  56. data/lib/cmdx/faults.rb +11 -0
  57. data/lib/cmdx/immutator.rb +21 -0
  58. data/lib/cmdx/lazy_struct.rb +79 -0
  59. data/lib/cmdx/log_formatters/json.rb +13 -0
  60. data/lib/cmdx/log_formatters/key_value.rb +13 -0
  61. data/lib/cmdx/log_formatters/line.rb +14 -0
  62. data/lib/cmdx/log_formatters/logstash.rb +18 -0
  63. data/lib/cmdx/log_formatters/raw.rb +13 -0
  64. data/lib/cmdx/logger.rb +16 -0
  65. data/lib/cmdx/parameter.rb +101 -0
  66. data/lib/cmdx/parameter_inspector.rb +23 -0
  67. data/lib/cmdx/parameter_serializer.rb +20 -0
  68. data/lib/cmdx/parameter_validator.rb +19 -0
  69. data/lib/cmdx/parameter_value.rb +136 -0
  70. data/lib/cmdx/parameters.rb +34 -0
  71. data/lib/cmdx/parameters_inspector.rb +13 -0
  72. data/lib/cmdx/parameters_serializer.rb +13 -0
  73. data/lib/cmdx/railtie.rb +32 -0
  74. data/lib/cmdx/result.rb +170 -0
  75. data/lib/cmdx/result_inspector.rb +31 -0
  76. data/lib/cmdx/result_logger.rb +22 -0
  77. data/lib/cmdx/result_serializer.rb +38 -0
  78. data/lib/cmdx/run.rb +33 -0
  79. data/lib/cmdx/run_inspector.rb +21 -0
  80. data/lib/cmdx/run_serializer.rb +16 -0
  81. data/lib/cmdx/task.rb +151 -0
  82. data/lib/cmdx/task_hook.rb +18 -0
  83. data/lib/cmdx/utils/datetime_formatter.rb +17 -0
  84. data/lib/cmdx/utils/method_name.rb +24 -0
  85. data/lib/cmdx/utils/runtime.rb +19 -0
  86. data/lib/cmdx/validators/custom.rb +20 -0
  87. data/lib/cmdx/validators/exclusion.rb +51 -0
  88. data/lib/cmdx/validators/format.rb +27 -0
  89. data/lib/cmdx/validators/inclusion.rb +51 -0
  90. data/lib/cmdx/validators/length.rb +114 -0
  91. data/lib/cmdx/validators/numeric.rb +114 -0
  92. data/lib/cmdx/validators/presence.rb +27 -0
  93. data/lib/cmdx/version.rb +7 -0
  94. data/lib/cmdx.rb +80 -0
  95. data/lib/generators/cmdx/batch_generator.rb +30 -0
  96. data/lib/generators/cmdx/install_generator.rb +15 -0
  97. data/lib/generators/cmdx/task_generator.rb +30 -0
  98. data/lib/generators/cmdx/templates/batch.rb.tt +7 -0
  99. data/lib/generators/cmdx/templates/install.rb +23 -0
  100. data/lib/generators/cmdx/templates/task.rb.tt +9 -0
  101. data/lib/locales/en.yml +36 -0
  102. data/lib/locales/es.yml +36 -0
  103. metadata +288 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Date
6
+
7
+ ANALOG_TYPES = %w[Date DateTime Time].freeze
8
+
9
+ module_function
10
+
11
+ def call(value, options = {})
12
+ return value if ANALOG_TYPES.include?(value.class.name)
13
+ return ::Date.strptime(value, options[:format]) if options[:format]
14
+
15
+ ::Date.parse(value)
16
+ rescue TypeError, ::Date::Error
17
+ raise CoercionError, I18n.t(
18
+ "cmdx.coercions.into_a",
19
+ type: "date",
20
+ default: "could not coerce into a date"
21
+ )
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module DateTime
6
+
7
+ ANALOG_TYPES = %w[Date DateTime Time].freeze
8
+
9
+ module_function
10
+
11
+ def call(value, options = {})
12
+ return value if ANALOG_TYPES.include?(value.class.name)
13
+ return ::DateTime.strptime(value, options[:format]) if options[:format]
14
+
15
+ ::DateTime.parse(value)
16
+ rescue TypeError, ::Date::Error
17
+ raise CoercionError, I18n.t(
18
+ "cmdx.coercions.into_a",
19
+ type: "datetime",
20
+ default: "could not coerce into a datetime"
21
+ )
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Float
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ Float(value)
11
+ rescue ArgumentError, TypeError
12
+ raise CoercionError, I18n.t(
13
+ "cmdx.coercions.into_a",
14
+ type: "float",
15
+ default: "could not coerce into a float"
16
+ )
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Hash
6
+
7
+ extend self
8
+
9
+ def call(value, _options = {})
10
+ case value.class.name
11
+ when "Hash", "ActionController::Parameters" then value
12
+ when "Array" then ::Hash[*value]
13
+ else raise_coercion_error!
14
+ end
15
+ rescue ArgumentError, TypeError
16
+ raise_coercion_error!
17
+ end
18
+
19
+ private
20
+
21
+ def raise_coercion_error!
22
+ raise CoercionError, I18n.t(
23
+ "cmdx.coercions.into_a",
24
+ type: "hash",
25
+ default: "could not coerce into a hash"
26
+ )
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Integer
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ Integer(value)
11
+ rescue ArgumentError, TypeError
12
+ raise CoercionError, I18n.t(
13
+ "cmdx.coercions.into_an",
14
+ type: "integer",
15
+ default: "could not coerce into an integer"
16
+ )
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Rational
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ Rational(value)
11
+ rescue ArgumentError, TypeError
12
+ raise CoercionError, I18n.t(
13
+ "cmdx.coercions.into_a",
14
+ type: "rational",
15
+ default: "could not coerce into a rational"
16
+ )
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module String
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ String(value)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Time
6
+
7
+ ANALOG_TYPES = %w[Date DateTime Time].freeze
8
+
9
+ module_function
10
+
11
+ def call(value, options = {})
12
+ return value if ANALOG_TYPES.include?(value.class.name)
13
+ return ::Time.strptime(value, options[:format]) if options[:format]
14
+
15
+ ::Time.parse(value)
16
+ rescue ArgumentError, TypeError
17
+ raise CoercionError, I18n.t(
18
+ "cmdx.coercions.into_a",
19
+ type: "time",
20
+ default: "could not coerce into a time"
21
+ )
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Coercions
5
+ module Virtual
6
+
7
+ module_function
8
+
9
+ def call(value, _options = {})
10
+ value
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+
5
+ module_function
6
+
7
+ def configuration
8
+ @configuration || reset_configuration!
9
+ end
10
+
11
+ def configure
12
+ yield(configuration)
13
+ end
14
+
15
+ def reset_configuration!
16
+ @configuration = LazyStruct.new(
17
+ logger: ::Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new),
18
+ task_halt: CMDx::Result::FAILED,
19
+ task_timeout: nil,
20
+ batch_halt: CMDx::Result::FAILED,
21
+ batch_timeout: nil
22
+ )
23
+ end
24
+
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ class Context < LazyStruct
5
+
6
+ attr_reader :run
7
+
8
+ def self.build(context = {})
9
+ return context if context.is_a?(self) && !context.frozen?
10
+
11
+ new(context)
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module CoreExt
5
+ module HashExtensions
6
+
7
+ def __cmdx_fetch(key)
8
+ case key
9
+ when Symbol then fetch(key) { self[key.to_s] }
10
+ when String then fetch(key) { self[key.to_sym] }
11
+ else self[key]
12
+ end
13
+ end
14
+
15
+ def __cmdx_key?(key)
16
+ key?(key) || key?(
17
+ case key
18
+ when Symbol then key.to_s
19
+ when String then key.to_sym
20
+ end
21
+ )
22
+ rescue NoMethodError
23
+ false
24
+ end
25
+
26
+ def __cmdx_respond_to?(key, include_private = false)
27
+ respond_to?(key.to_sym, include_private) || __cmdx_key?(key)
28
+ rescue NoMethodError
29
+ __cmdx_key?(key)
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+
36
+ Hash.include(CMDx::CoreExt::HashExtensions)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module CoreExt
5
+ module ModuleExtensions
6
+
7
+ def __cmdx_attr_delegator(*methods, **options)
8
+ methods.each do |method|
9
+ method_name = Utils::MethodName.call(method, options.fetch(:to), options)
10
+
11
+ define_method(method_name) do |*args, **kwargs, &block|
12
+ object = (options[:to] == :class ? self.class : send(options[:to]))
13
+
14
+ unless options[:allow_missing] || object.respond_to?(method, true)
15
+ raise NoMethodError,
16
+ "undefined method `#{method}' for #{options[:to]}"
17
+ end
18
+
19
+ object.send(method, *args, **kwargs, &block)
20
+ end
21
+
22
+ case options
23
+ in { protected: true } then send(:protected, method_name)
24
+ in { private: true } then send(:private, method_name)
25
+ else # Leave public
26
+ end
27
+ end
28
+ end
29
+
30
+ def __cmdx_attr_setting(method, **options)
31
+ define_singleton_method(method) do
32
+ @cmd_facets ||= {}
33
+ return @cmd_facets[method] if @cmd_facets.key?(method)
34
+
35
+ value = superclass.__cmdx_try(method)
36
+ return @cmd_facets[method] = value.dup unless value.nil?
37
+
38
+ default = options[:default]
39
+ value = default.__cmdx_call
40
+ @cmd_facets[method] = default.is_a?(Proc) ? value : value.dup
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+
48
+ Module.include(CMDx::CoreExt::ModuleExtensions)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module CoreExt
5
+ module ObjectExtensions
6
+
7
+ alias __cmdx_respond_to? respond_to?
8
+
9
+ def __cmdx_try(key, ...)
10
+ if key.is_a?(Proc)
11
+ return instance_eval(&key) unless is_a?(Module) || key.inspect.include?("(lambda)")
12
+
13
+ key.call(...)
14
+ elsif respond_to?(key, true)
15
+ send(key, ...)
16
+ elsif is_a?(Hash)
17
+ __cmdx_fetch(key)
18
+ end
19
+ end
20
+
21
+ def __cmdx_eval(options = {})
22
+ if options[:if] && options[:unless]
23
+ __cmdx_try(options[:if]) && !__cmdx_try(options[:unless])
24
+ elsif options[:if]
25
+ __cmdx_try(options[:if])
26
+ elsif options[:unless]
27
+ !__cmdx_try(options[:unless])
28
+ else
29
+ options.fetch(:default, true)
30
+ end
31
+ end
32
+
33
+ def __cmdx_yield(key, ...)
34
+ if key.is_a?(Symbol) || key.is_a?(String)
35
+ return key unless respond_to?(key, true)
36
+
37
+ send(key, ...)
38
+ elsif is_a?(Hash) || key.is_a?(Proc)
39
+ __cmdx_try(key, ...)
40
+ else
41
+ key
42
+ end
43
+ end
44
+
45
+ def __cmdx_call(...)
46
+ return self unless respond_to?(:call)
47
+
48
+ call(...)
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+
55
+ Object.include(CMDx::CoreExt::ObjectExtensions)
data/lib/cmdx/error.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+
5
+ # Base of all CMDx errors
6
+ Error = Class.new(StandardError)
7
+
8
+ # Raised when value could not be coerced to defined type
9
+ CoercionError = Class.new(Error)
10
+
11
+ # Raised when call execution time exceeds max allowed
12
+ TimeoutError = Class.new(Interrupt)
13
+
14
+ # Raised when call method not defined in implementing class
15
+ UndefinedCallError = Class.new(Error)
16
+
17
+ # Raised when unknown coercion type
18
+ UnknownCoercionError = Class.new(Error)
19
+
20
+ # Raised when value failed a validation
21
+ ValidationError = Class.new(Error)
22
+
23
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ class Errors
5
+
6
+ __cmdx_attr_delegator :clear, :delete, :each, :empty?, :key?, :keys, :size, :values, to: :errors
7
+
8
+ attr_reader :errors
9
+
10
+ alias attribute_names keys
11
+ alias blank? empty?
12
+ alias valid? empty?
13
+ alias has_key? key?
14
+ alias include? key?
15
+
16
+ def initialize
17
+ @errors = {}
18
+ end
19
+
20
+ def add(key, value)
21
+ errors[key] ||= []
22
+ errors[key] << value
23
+ errors[key].uniq!
24
+ end
25
+ alias []= add
26
+
27
+ def added?(key, val)
28
+ return false unless key?(key)
29
+
30
+ errors[key].include?(val)
31
+ end
32
+ alias of_kind? added?
33
+
34
+ def each
35
+ errors.each_key do |key|
36
+ errors[key].each { |val| yield(key, val) }
37
+ end
38
+ end
39
+
40
+ def full_message(key, value)
41
+ "#{key} #{value}"
42
+ end
43
+
44
+ def full_messages
45
+ errors.each_with_object([]) do |(key, arr), memo|
46
+ arr.each { |val| memo << full_message(key, val) }
47
+ end
48
+ end
49
+ alias to_a full_messages
50
+
51
+ def full_messages_for(key)
52
+ return [] unless key?(key)
53
+
54
+ errors[key].map { |val| full_message(key, val) }
55
+ end
56
+
57
+ def invalid?
58
+ !valid?
59
+ end
60
+
61
+ def merge!(hash)
62
+ errors.merge!(hash) do |_, arr1, arr2|
63
+ arr3 = arr1 + arr2
64
+ arr3.uniq!
65
+ arr3
66
+ end
67
+ end
68
+
69
+ def messages_for(key)
70
+ return [] unless key?(key)
71
+
72
+ errors[key]
73
+ end
74
+ alias [] messages_for
75
+
76
+ def present?
77
+ !blank?
78
+ end
79
+
80
+ def to_hash(full_messages = false)
81
+ return errors unless full_messages
82
+
83
+ errors.each_with_object({}) do |(key, arr), memo|
84
+ memo[key] = arr.map { |val| full_message(key, val) }
85
+ end
86
+ end
87
+ alias messages to_hash
88
+ alias group_by_attribute to_hash
89
+ alias as_json to_hash
90
+
91
+ end
92
+ end
data/lib/cmdx/fault.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ class Fault < Error
5
+
6
+ __cmdx_attr_delegator :task, :run, :context, to: :result
7
+
8
+ attr_reader :result
9
+
10
+ def initialize(result)
11
+ @result = result
12
+ super(result.metadata[:reason] || I18n.t("cmdx.faults.unspecified", default: "no reason given"))
13
+ end
14
+
15
+ class << self
16
+
17
+ def build(result)
18
+ fault = CMDx.const_get(result.status.capitalize)
19
+ fault.new(result)
20
+ end
21
+
22
+ def for?(*tasks)
23
+ temp_fault = Class.new(self) do
24
+ def self.===(other)
25
+ other.is_a?(superclass) && @tasks.any? { |task| other.task.is_a?(task) }
26
+ end
27
+ end
28
+
29
+ temp_fault.tap { |c| c.instance_variable_set(:@tasks, tasks) }
30
+ end
31
+
32
+ def matches?(&block)
33
+ raise ArgumentError, "a block is required" unless block_given?
34
+
35
+ temp_fault = Class.new(self) do
36
+ def self.===(other)
37
+ other.is_a?(superclass) && @block.call(other)
38
+ end
39
+ end
40
+
41
+ temp_fault.tap { |c| c.instance_variable_set(:@block, block) }
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+
5
+ # Raised when halting task processing with skipped context
6
+ Skipped = Class.new(Fault)
7
+
8
+ # Raised when halting task processing with failed context
9
+ Failed = Class.new(Fault)
10
+
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module Immutator
5
+
6
+ module_function
7
+
8
+ def call(task)
9
+ # Stubbing on frozen objects is not allowed
10
+ return if (ENV.fetch("RAILS_ENV", nil) || ENV.fetch("RACK_ENV", nil)) == "test"
11
+
12
+ task.freeze
13
+ task.result.freeze
14
+ return unless task.result.index.zero?
15
+
16
+ task.context.freeze
17
+ task.run.freeze
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ class LazyStruct
5
+
6
+ def initialize(args = {})
7
+ unless args.respond_to?(:to_h)
8
+ raise ArgumentError,
9
+ "must be respond to `to_h`"
10
+ end
11
+
12
+ @table = args.transform_keys(&:to_sym)
13
+ end
14
+
15
+ def [](key)
16
+ @table[key.to_sym]
17
+ end
18
+
19
+ def fetch!(key, ...)
20
+ @table.fetch(key.to_sym, ...)
21
+ end
22
+
23
+ def store!(key, value)
24
+ @table[key.to_sym] = value
25
+ end
26
+ alias []= store!
27
+
28
+ def merge!(args = {})
29
+ args.to_h.each { |key, value| store!(key, value) }
30
+ self
31
+ end
32
+
33
+ def delete!(key, &)
34
+ @table.delete(key.to_sym, &)
35
+ end
36
+ alias delete_field! delete!
37
+
38
+ def eql?(other)
39
+ other.is_a?(self.class) && (to_h == other.to_h)
40
+ end
41
+ alias == eql?
42
+
43
+ def dig(key, *keys)
44
+ begin
45
+ key = key.to_sym
46
+ rescue NoMethodError
47
+ raise TypeError, "#{key} is not a symbol nor a string"
48
+ end
49
+
50
+ @table.dig(key, *keys)
51
+ end
52
+
53
+ def each_pair(&)
54
+ @table.each_pair(&)
55
+ end
56
+
57
+ def to_h(&)
58
+ @table.to_h(&)
59
+ end
60
+
61
+ def inspect
62
+ "#<#{self.class}#{@table.map { |key, value| ":#{key}=#{value.inspect}" }.join(' ')}>"
63
+ end
64
+ alias to_s inspect
65
+
66
+ private
67
+
68
+ def method_missing(method_name, *args, **_kwargs, &)
69
+ @table.fetch(method_name.to_sym) do
70
+ store!(method_name[0..-2], args.first) if method_name.end_with?("=")
71
+ end
72
+ end
73
+
74
+ def respond_to_missing?(method_name, include_private = false)
75
+ @table.key?(method_name.to_sym) || super
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module LogFormatters
5
+ class Json
6
+
7
+ def call(_severity, _time, _progname, message)
8
+ JSON.dump(message)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CMDx
4
+ module LogFormatters
5
+ class KeyValue
6
+
7
+ def call(_severity, _time, _progname, message)
8
+ message.map { |k, v| "#{k}=#{v}" }.join(" ")
9
+ end
10
+
11
+ end
12
+ end
13
+ end