cmdx 1.9.0 → 1.9.1
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 +4 -4
- data/.cursor/prompts/yardoc.md +1 -0
- data/CHANGELOG.md +6 -0
- data/LLM.md +9 -0
- data/README.md +6 -1
- data/docs/getting_started.md +9 -0
- data/docs/index.md +13 -1
- data/lib/cmdx/attribute.rb +82 -1
- data/lib/cmdx/attribute_registry.rb +20 -0
- data/lib/cmdx/attribute_value.rb +25 -0
- data/lib/cmdx/callback_registry.rb +19 -0
- data/lib/cmdx/chain.rb +34 -1
- data/lib/cmdx/coercion_registry.rb +18 -0
- data/lib/cmdx/coercions/array.rb +2 -0
- data/lib/cmdx/coercions/big_decimal.rb +3 -0
- data/lib/cmdx/coercions/boolean.rb +5 -0
- data/lib/cmdx/coercions/complex.rb +2 -0
- data/lib/cmdx/coercions/date.rb +4 -0
- data/lib/cmdx/coercions/date_time.rb +5 -0
- data/lib/cmdx/coercions/float.rb +2 -0
- data/lib/cmdx/coercions/hash.rb +2 -0
- data/lib/cmdx/coercions/integer.rb +2 -0
- data/lib/cmdx/coercions/rational.rb +2 -0
- data/lib/cmdx/coercions/string.rb +2 -0
- data/lib/cmdx/coercions/symbol.rb +2 -0
- data/lib/cmdx/coercions/time.rb +5 -0
- data/lib/cmdx/configuration.rb +111 -3
- data/lib/cmdx/context.rb +36 -0
- data/lib/cmdx/deprecator.rb +3 -0
- data/lib/cmdx/errors.rb +22 -0
- data/lib/cmdx/executor.rb +43 -0
- data/lib/cmdx/faults.rb +14 -0
- data/lib/cmdx/identifier.rb +2 -0
- data/lib/cmdx/locale.rb +3 -0
- data/lib/cmdx/log_formatters/json.rb +2 -0
- data/lib/cmdx/log_formatters/key_value.rb +2 -0
- data/lib/cmdx/log_formatters/line.rb +2 -0
- data/lib/cmdx/log_formatters/logstash.rb +2 -0
- data/lib/cmdx/log_formatters/raw.rb +2 -0
- data/lib/cmdx/middleware_registry.rb +20 -0
- data/lib/cmdx/middlewares/correlate.rb +11 -0
- data/lib/cmdx/middlewares/runtime.rb +4 -0
- data/lib/cmdx/middlewares/timeout.rb +4 -0
- data/lib/cmdx/pipeline.rb +20 -1
- data/lib/cmdx/railtie.rb +4 -0
- data/lib/cmdx/result.rb +123 -1
- data/lib/cmdx/task.rb +91 -1
- data/lib/cmdx/utils/call.rb +2 -0
- data/lib/cmdx/utils/condition.rb +3 -0
- data/lib/cmdx/utils/format.rb +5 -0
- data/lib/cmdx/validator_registry.rb +18 -0
- data/lib/cmdx/validators/exclusion.rb +2 -0
- data/lib/cmdx/validators/format.rb +2 -0
- data/lib/cmdx/validators/inclusion.rb +2 -0
- data/lib/cmdx/validators/length.rb +14 -0
- data/lib/cmdx/validators/numeric.rb +14 -0
- data/lib/cmdx/validators/presence.rb +2 -0
- data/lib/cmdx/version.rb +4 -1
- data/lib/cmdx/workflow.rb +10 -0
- data/lib/cmdx.rb +8 -0
- data/lib/generators/cmdx/locale_generator.rb +0 -1
- metadata +1 -1
| @@ -10,6 +10,7 @@ module CMDx | |
| 10 10 |  | 
| 11 11 | 
             
                  extend self
         | 
| 12 12 |  | 
| 13 | 
            +
                  # @rbs DEFAULT_PRECISION: Integer
         | 
| 13 14 | 
             
                  DEFAULT_PRECISION = 14
         | 
| 14 15 |  | 
| 15 16 | 
             
                  # Converts a value to a BigDecimal
         | 
| @@ -28,6 +29,8 @@ module CMDx | |
| 28 29 | 
             
                  # @example Convert other numeric types
         | 
| 29 30 | 
             
                  #   BigDecimal.call(42)                         # => #<BigDecimal:7f8b8c0d8e0f '0.42E2',9(18)>
         | 
| 30 31 | 
             
                  #   BigDecimal.call(3.14159)                    # => #<BigDecimal:7f8b8c0d8e0f '0.314159E1',9(18)>
         | 
| 32 | 
            +
                  #
         | 
| 33 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> BigDecimal
         | 
| 31 34 | 
             
                  def call(value, options = {})
         | 
| 32 35 | 
             
                    BigDecimal(value, options[:precision] || DEFAULT_PRECISION)
         | 
| 33 36 | 
             
                  rescue ArgumentError, TypeError
         | 
| @@ -10,7 +10,10 @@ module CMDx | |
| 10 10 |  | 
| 11 11 | 
             
                  extend self
         | 
| 12 12 |  | 
| 13 | 
            +
                  # @rbs FALSEY: Regexp
         | 
| 13 14 | 
             
                  FALSEY = /^(false|f|no|n|0)$/i
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  # @rbs TRUTHY: Regexp
         | 
| 14 17 | 
             
                  TRUTHY = /^(true|t|yes|y|1)$/i
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Converts a value to a Boolean
         | 
| @@ -34,6 +37,8 @@ module CMDx | |
| 34 37 | 
             
                  # @example Handle case-insensitive input
         | 
| 35 38 | 
             
                  #   Boolean.call("TRUE")   # => true
         | 
| 36 39 | 
             
                  #   Boolean.call("False")  # => false
         | 
| 40 | 
            +
                  #
         | 
| 41 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> bool
         | 
| 37 42 | 
             
                  def call(value, options = {})
         | 
| 38 43 | 
             
                    case value.to_s.downcase
         | 
| 39 44 | 
             
                    when FALSEY then false
         | 
| @@ -26,6 +26,8 @@ module CMDx | |
| 26 26 | 
             
                  #   Complex.call(5)                          # => (5+0i)
         | 
| 27 27 | 
             
                  #   Complex.call(3.14)                       # => (3.14+0i)
         | 
| 28 28 | 
             
                  #   Complex.call(Complex(1, 2))              # => (1+2i)
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Complex
         | 
| 29 31 | 
             
                  def call(value, options = {})
         | 
| 30 32 | 
             
                    Complex(value)
         | 
| 31 33 | 
             
                  rescue ArgumentError, TypeError
         | 
    
        data/lib/cmdx/coercions/date.rb
    CHANGED
    
    | @@ -12,6 +12,8 @@ module CMDx | |
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 14 | 
             
                  # Types that are already date-like and don't need conversion
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @rbs ANALOG_TYPES: Array[String]
         | 
| 15 17 | 
             
                  ANALOG_TYPES = %w[Date DateTime Time].freeze
         | 
| 16 18 |  | 
| 17 19 | 
             
                  # Converts a value to a Date object
         | 
| @@ -33,6 +35,8 @@ module CMDx | |
| 33 35 | 
             
                  # @example Return existing Date objects unchanged
         | 
| 34 36 | 
             
                  #   Date.call(Date.new(2023, 12, 25)) # => #<Date: 2023-12-25>
         | 
| 35 37 | 
             
                  #   Date.call(DateTime.new(2023, 12, 25)) # => #<Date: 2023-12-25>
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Date
         | 
| 36 40 | 
             
                  def call(value, options = {})
         | 
| 37 41 | 
             
                    return value if ANALOG_TYPES.include?(value.class.name)
         | 
| 38 42 | 
             
                    return ::Date.strptime(value, options[:strptime]) if options[:strptime]
         | 
| @@ -11,6 +11,9 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 | 
            +
                  # Types that are already date-time-like and don't need conversion
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @rbs ANALOG_TYPES: Array[String]
         | 
| 14 17 | 
             
                  ANALOG_TYPES = %w[Date DateTime Time].freeze
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Converts a value to a DateTime
         | 
| @@ -32,6 +35,8 @@ module CMDx | |
| 32 35 | 
             
                  # @example Convert existing date objects
         | 
| 33 36 | 
             
                  #   DateTime.call(Date.new(2023, 12, 25))     # => #<DateTime: 2023-12-25T00:00:00+00:00>
         | 
| 34 37 | 
             
                  #   DateTime.call(Time.new(2023, 12, 25))     # => #<DateTime: 2023-12-25T00:00:00+00:00>
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> DateTime
         | 
| 35 40 | 
             
                  def call(value, options = {})
         | 
| 36 41 | 
             
                    return value if ANALOG_TYPES.include?(value.class.name)
         | 
| 37 42 | 
             
                    return ::DateTime.strptime(value, options[:strptime]) if options[:strptime]
         | 
    
        data/lib/cmdx/coercions/float.rb
    CHANGED
    
    | @@ -30,6 +30,8 @@ module CMDx | |
| 30 30 | 
             
                  #   Float.call(BigDecimal("123.456")) # => 123.456
         | 
| 31 31 | 
             
                  #   Float.call(Rational(3, 4))       # => 0.75
         | 
| 32 32 | 
             
                  #   Float.call(Complex(5.0, 0))      # => 5.0
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Float
         | 
| 33 35 | 
             
                  def call(value, options = {})
         | 
| 34 36 | 
             
                    Float(value)
         | 
| 35 37 | 
             
                  rescue ArgumentError, RangeError, TypeError
         | 
    
        data/lib/cmdx/coercions/hash.rb
    CHANGED
    
    | @@ -30,6 +30,8 @@ module CMDx | |
| 30 30 | 
             
                  #   Hash.call([:a, 1, :b, 2]) # => {a: 1, b: 2}
         | 
| 31 31 | 
             
                  # @example Coerce from JSON string
         | 
| 32 32 | 
             
                  #   Hash.call('{"key": "value"}') # => {"key" => "value"}
         | 
| 33 | 
            +
                  #
         | 
| 34 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Hash[untyped, untyped]
         | 
| 33 35 | 
             
                  def call(value, options = {})
         | 
| 34 36 | 
             
                    if value.nil?
         | 
| 35 37 | 
             
                      {}
         | 
| @@ -34,6 +34,8 @@ module CMDx | |
| 34 34 | 
             
                  #   Integer.call(nil)       # => 0
         | 
| 35 35 | 
             
                  #   Integer.call(false)     # => 0
         | 
| 36 36 | 
             
                  #   Integer.call(true)      # => 1
         | 
| 37 | 
            +
                  #
         | 
| 38 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Integer
         | 
| 37 39 | 
             
                  def call(value, options = {})
         | 
| 38 40 | 
             
                    Integer(value)
         | 
| 39 41 | 
             
                  rescue ArgumentError, FloatDomainError, RangeError, TypeError
         | 
| @@ -33,6 +33,8 @@ module CMDx | |
| 33 33 | 
             
                  #   Rational.call("")        # => (0/1)
         | 
| 34 34 | 
             
                  #   Rational.call(nil)       # => (0/1)
         | 
| 35 35 | 
             
                  #   Rational.call(0)         # => (0/1)
         | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Rational
         | 
| 36 38 | 
             
                  def call(value, options = {})
         | 
| 37 39 | 
             
                    Rational(value)
         | 
| 38 40 | 
             
                  rescue ArgumentError, FloatDomainError, RangeError, TypeError, ZeroDivisionError
         | 
| @@ -25,6 +25,8 @@ module CMDx | |
| 25 25 | 
             
                  #   Symbol.call("user_id")         # => :user_id
         | 
| 26 26 | 
             
                  #   Symbol.call("")                # => :""
         | 
| 27 27 | 
             
                  #   Symbol.call(:existing)         # => :existing
         | 
| 28 | 
            +
                  #
         | 
| 29 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Symbol
         | 
| 28 30 | 
             
                  def call(value, options = {})
         | 
| 29 31 | 
             
                    value.to_sym
         | 
| 30 32 | 
             
                  rescue NoMethodError
         | 
    
        data/lib/cmdx/coercions/time.rb
    CHANGED
    
    | @@ -11,6 +11,9 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                  extend self
         | 
| 13 13 |  | 
| 14 | 
            +
                  # Types that are already time-like and don't need conversion
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # @rbs ANALOG_TYPES: Array[String]
         | 
| 14 17 | 
             
                  ANALOG_TYPES = %w[DateTime Time].freeze
         | 
| 15 18 |  | 
| 16 19 | 
             
                  # Converts a value to a Time object
         | 
| @@ -34,6 +37,8 @@ module CMDx | |
| 34 37 | 
             
                  # @example Convert strings with custom format
         | 
| 35 38 | 
             
                  #   Time.call("25/12/2023", strptime: "%d/%m/%Y")  # => Time object
         | 
| 36 39 | 
             
                  #   Time.call("12-25-2023", strptime: "%m-%d-%Y")  # => Time object
         | 
| 40 | 
            +
                  #
         | 
| 41 | 
            +
                  # @rbs (untyped value, ?Hash[Symbol, untyped] options) -> Time
         | 
| 37 42 | 
             
                  def call(value, options = {})
         | 
| 38 43 | 
             
                    return value if ANALOG_TYPES.include?(value.class.name)
         | 
| 39 44 | 
             
                    return value.to_time if value.respond_to?(:to_time)
         | 
    
        data/lib/cmdx/configuration.rb
    CHANGED
    
    | @@ -6,11 +6,109 @@ module CMDx | |
| 6 6 | 
             
              # callbacks, coercions, validators, breakpoints, backtraces, and logging.
         | 
| 7 7 | 
             
              class Configuration
         | 
| 8 8 |  | 
| 9 | 
            +
                # @rbs DEFAULT_BREAKPOINTS: Array[String]
         | 
| 9 10 | 
             
                DEFAULT_BREAKPOINTS = %w[failed].freeze
         | 
| 10 11 |  | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 12 | 
            +
                # Returns the middleware registry for task execution.
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # @return [MiddlewareRegistry] The middleware registry
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @example
         | 
| 17 | 
            +
                #   config.middlewares.register(CustomMiddleware)
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # @rbs @middlewares: MiddlewareRegistry
         | 
| 20 | 
            +
                attr_accessor :middlewares
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # Returns the callback registry for task lifecycle hooks.
         | 
| 23 | 
            +
                #
         | 
| 24 | 
            +
                # @return [CallbackRegistry] The callback registry
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # @example
         | 
| 27 | 
            +
                #   config.callbacks.register(:before_execution, :log_start)
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @rbs @callbacks: CallbackRegistry
         | 
| 30 | 
            +
                attr_accessor :callbacks
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                # Returns the coercion registry for type conversions.
         | 
| 33 | 
            +
                #
         | 
| 34 | 
            +
                # @return [CoercionRegistry] The coercion registry
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # @example
         | 
| 37 | 
            +
                #   config.coercions.register(:custom, CustomCoercion)
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                # @rbs @coercions: CoercionRegistry
         | 
| 40 | 
            +
                attr_accessor :coercions
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # Returns the validator registry for attribute validation.
         | 
| 43 | 
            +
                #
         | 
| 44 | 
            +
                # @return [ValidatorRegistry] The validator registry
         | 
| 45 | 
            +
                #
         | 
| 46 | 
            +
                # @example
         | 
| 47 | 
            +
                #   config.validators.register(:email, EmailValidator)
         | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                # @rbs @validators: ValidatorRegistry
         | 
| 50 | 
            +
                attr_accessor :validators
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                # Returns the breakpoint statuses for task execution interruption.
         | 
| 53 | 
            +
                #
         | 
| 54 | 
            +
                # @return [Array<String>] Array of status names that trigger breakpoints
         | 
| 55 | 
            +
                #
         | 
| 56 | 
            +
                # @example
         | 
| 57 | 
            +
                #   config.task_breakpoints = ["failed", "skipped"]
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # @rbs @task_breakpoints: Array[String]
         | 
| 60 | 
            +
                attr_accessor :task_breakpoints
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                # Returns the breakpoint statuses for workflow execution interruption.
         | 
| 63 | 
            +
                #
         | 
| 64 | 
            +
                # @return [Array<String>] Array of status names that trigger breakpoints
         | 
| 65 | 
            +
                #
         | 
| 66 | 
            +
                # @example
         | 
| 67 | 
            +
                #   config.workflow_breakpoints = ["failed", "skipped"]
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # @rbs @task_breakpoints: Array[String]
         | 
| 70 | 
            +
                # @rbs @workflow_breakpoints: Array[String]
         | 
| 71 | 
            +
                attr_accessor :workflow_breakpoints
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                # Returns the logger instance for CMDx operations.
         | 
| 74 | 
            +
                #
         | 
| 75 | 
            +
                # @return [Logger] The logger instance
         | 
| 76 | 
            +
                #
         | 
| 77 | 
            +
                # @example
         | 
| 78 | 
            +
                #   config.logger.level = Logger::DEBUG
         | 
| 79 | 
            +
                #
         | 
| 80 | 
            +
                # @rbs @logger: Logger
         | 
| 81 | 
            +
                attr_accessor :logger
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                # Returns whether to log backtraces for failed tasks.
         | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                # @return [Boolean] true if backtraces should be logged
         | 
| 86 | 
            +
                #
         | 
| 87 | 
            +
                # @example
         | 
| 88 | 
            +
                #   config.backtrace = true
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # @rbs @backtrace: bool
         | 
| 91 | 
            +
                attr_accessor :backtrace
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                # Returns the proc used to clean backtraces before logging.
         | 
| 94 | 
            +
                #
         | 
| 95 | 
            +
                # @return [Proc, nil] The backtrace cleaner proc, or nil if not set
         | 
| 96 | 
            +
                #
         | 
| 97 | 
            +
                # @example
         | 
| 98 | 
            +
                #   config.backtrace_cleaner = ->(bt) { bt.first(5) }
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @rbs @backtrace_cleaner: (Proc | nil)
         | 
| 101 | 
            +
                attr_accessor :backtrace_cleaner
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                # Returns the proc called when exceptions occur during execution.
         | 
| 104 | 
            +
                #
         | 
| 105 | 
            +
                # @return [Proc, nil] The exception handler proc, or nil if not set
         | 
| 106 | 
            +
                #
         | 
| 107 | 
            +
                # @example
         | 
| 108 | 
            +
                #   config.exception_handler = ->(task, error) { Sentry.capture_exception(error) }
         | 
| 109 | 
            +
                #
         | 
| 110 | 
            +
                # @rbs @exception_handler: (Proc | nil)
         | 
| 111 | 
            +
                attr_accessor :exception_handler
         | 
| 14 112 |  | 
| 15 113 | 
             
                # Initializes a new Configuration instance with default values.
         | 
| 16 114 | 
             
                #
         | 
| @@ -23,6 +121,8 @@ module CMDx | |
| 23 121 | 
             
                #   config = Configuration.new
         | 
| 24 122 | 
             
                #   config.middlewares.class # => MiddlewareRegistry
         | 
| 25 123 | 
             
                #   config.task_breakpoints # => ["failed"]
         | 
| 124 | 
            +
                #
         | 
| 125 | 
            +
                # @rbs () -> void
         | 
| 26 126 | 
             
                def initialize
         | 
| 27 127 | 
             
                  @middlewares = MiddlewareRegistry.new
         | 
| 28 128 | 
             
                  @callbacks = CallbackRegistry.new
         | 
| @@ -52,6 +152,8 @@ module CMDx | |
| 52 152 | 
             
                #   config = Configuration.new
         | 
| 53 153 | 
             
                #   config.to_h
         | 
| 54 154 | 
             
                #   # => { middlewares: #<MiddlewareRegistry>, callbacks: #<CallbackRegistry>, ... }
         | 
| 155 | 
            +
                #
         | 
| 156 | 
            +
                # @rbs () -> Hash[Symbol, untyped]
         | 
| 55 157 | 
             
                def to_h
         | 
| 56 158 | 
             
                  {
         | 
| 57 159 | 
             
                    middlewares: @middlewares,
         | 
| @@ -78,6 +180,8 @@ module CMDx | |
| 78 180 | 
             
              # @example
         | 
| 79 181 | 
             
              #   config = CMDx.configuration
         | 
| 80 182 | 
             
              #   config.middlewares # => #<MiddlewareRegistry>
         | 
| 183 | 
            +
              #
         | 
| 184 | 
            +
              # @rbs () -> Configuration
         | 
| 81 185 | 
             
              def configuration
         | 
| 82 186 | 
             
                return @configuration if @configuration
         | 
| 83 187 |  | 
| @@ -99,6 +203,8 @@ module CMDx | |
| 99 203 | 
             
              #     config.task_breakpoints = ["failed", "skipped"]
         | 
| 100 204 | 
             
              #     config.logger.level = Logger::DEBUG
         | 
| 101 205 | 
             
              #   end
         | 
| 206 | 
            +
              #
         | 
| 207 | 
            +
              # @rbs () { (Configuration) -> void } -> Configuration
         | 
| 102 208 | 
             
              def configure
         | 
| 103 209 | 
             
                raise ArgumentError, "block required" unless block_given?
         | 
| 104 210 |  | 
| @@ -114,6 +220,8 @@ module CMDx | |
| 114 220 | 
             
              # @example
         | 
| 115 221 | 
             
              #   CMDx.reset_configuration!
         | 
| 116 222 | 
             
              #   # Configuration is now reset to defaults
         | 
| 223 | 
            +
              #
         | 
| 224 | 
            +
              # @rbs () -> Configuration
         | 
| 117 225 | 
             
              def reset_configuration!
         | 
| 118 226 | 
             
                @configuration = Configuration.new
         | 
| 119 227 | 
             
              end
         | 
    
        data/lib/cmdx/context.rb
    CHANGED
    
    | @@ -11,6 +11,14 @@ module CMDx | |
| 11 11 |  | 
| 12 12 | 
             
                extend Forwardable
         | 
| 13 13 |  | 
| 14 | 
            +
                # Returns the internal hash storing context data.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @return [Hash{Symbol => Object}] The internal hash table
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @example
         | 
| 19 | 
            +
                #   context.table # => { name: "John", age: 30 }
         | 
| 20 | 
            +
                #
         | 
| 21 | 
            +
                # @rbs @table: Hash[Symbol, untyped]
         | 
| 14 22 | 
             
                attr_reader :table
         | 
| 15 23 | 
             
                alias to_h table
         | 
| 16 24 |  | 
| @@ -28,6 +36,8 @@ module CMDx | |
| 28 36 | 
             
                # @example
         | 
| 29 37 | 
             
                #   context = Context.new(name: "John", age: 30)
         | 
| 30 38 | 
             
                #   context[:name] # => "John"
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # @rbs (untyped args) -> void
         | 
| 31 41 | 
             
                def initialize(args = {})
         | 
| 32 42 | 
             
                  @table =
         | 
| 33 43 | 
             
                    if args.respond_to?(:to_hash)
         | 
| @@ -50,6 +60,8 @@ module CMDx | |
| 50 60 | 
             
                #   existing = Context.new(name: "John")
         | 
| 51 61 | 
             
                #   built = Context.build(existing) # reuses existing context
         | 
| 52 62 | 
             
                #   built.object_id == existing.object_id # => true
         | 
| 63 | 
            +
                #
         | 
| 64 | 
            +
                # @rbs (untyped context) -> Context
         | 
| 53 65 | 
             
                def self.build(context = {})
         | 
| 54 66 | 
             
                  if context.is_a?(self) && !context.frozen?
         | 
| 55 67 | 
             
                    context
         | 
| @@ -70,6 +82,8 @@ module CMDx | |
| 70 82 | 
             
                #   context = Context.new(name: "John")
         | 
| 71 83 | 
             
                #   context[:name] # => "John"
         | 
| 72 84 | 
             
                #   context["name"] # => "John" (automatically converted to symbol)
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @rbs ((String | Symbol) key) -> untyped
         | 
| 73 87 | 
             
                def [](key)
         | 
| 74 88 | 
             
                  table[key.to_sym]
         | 
| 75 89 | 
             
                end
         | 
| @@ -85,6 +99,8 @@ module CMDx | |
| 85 99 | 
             
                #   context = Context.new
         | 
| 86 100 | 
             
                #   context.store(:name, "John")
         | 
| 87 101 | 
             
                #   context[:name] # => "John"
         | 
| 102 | 
            +
                #
         | 
| 103 | 
            +
                # @rbs ((String | Symbol) key, untyped value) -> untyped
         | 
| 88 104 | 
             
                def store(key, value)
         | 
| 89 105 | 
             
                  table[key.to_sym] = value
         | 
| 90 106 | 
             
                end
         | 
| @@ -104,6 +120,8 @@ module CMDx | |
| 104 120 | 
             
                #   context.fetch(:name) # => "John"
         | 
| 105 121 | 
             
                #   context.fetch(:age, 25) # => 25
         | 
| 106 122 | 
             
                #   context.fetch(:city) { |key| "Unknown #{key}" } # => "Unknown city"
         | 
| 123 | 
            +
                #
         | 
| 124 | 
            +
                # @rbs ((String | Symbol) key, *untyped) ?{ ((String | Symbol)) -> untyped } -> untyped
         | 
| 107 125 | 
             
                def fetch(key, ...)
         | 
| 108 126 | 
             
                  table.fetch(key.to_sym, ...)
         | 
| 109 127 | 
             
                end
         | 
| @@ -122,6 +140,8 @@ module CMDx | |
| 122 140 | 
             
                #   context.fetch_or_store(:name, "Default") # => "John" (existing value)
         | 
| 123 141 | 
             
                #   context.fetch_or_store(:age, 25) # => 25 (stored and returned)
         | 
| 124 142 | 
             
                #   context.fetch_or_store(:city) { |key| "Unknown #{key}" } # => "Unknown city" (stored and returned)
         | 
| 143 | 
            +
                #
         | 
| 144 | 
            +
                # @rbs ((String | Symbol) key, ?untyped value) ?{ () -> untyped } -> untyped
         | 
| 125 145 | 
             
                def fetch_or_store(key, value = nil)
         | 
| 126 146 | 
             
                  table.fetch(key.to_sym) do
         | 
| 127 147 | 
             
                    table[key.to_sym] = block_given? ? yield : value
         | 
| @@ -139,6 +159,8 @@ module CMDx | |
| 139 159 | 
             
                #   context = Context.new(name: "John")
         | 
| 140 160 | 
             
                #   context.merge!(age: 30, city: "NYC")
         | 
| 141 161 | 
             
                #   context.to_h # => {name: "John", age: 30, city: "NYC"}
         | 
| 162 | 
            +
                #
         | 
| 163 | 
            +
                # @rbs (?untyped args) -> self
         | 
| 142 164 | 
             
                def merge!(args = {})
         | 
| 143 165 | 
             
                  args.to_h.each { |key, value| self[key.to_sym] = value }
         | 
| 144 166 | 
             
                  self
         | 
| @@ -156,6 +178,8 @@ module CMDx | |
| 156 178 | 
             
                #   context = Context.new(name: "John", age: 30)
         | 
| 157 179 | 
             
                #   context.delete!(:age) # => 30
         | 
| 158 180 | 
             
                #   context.delete!(:city) { |key| "Key #{key} not found" } # => "Key city not found"
         | 
| 181 | 
            +
                #
         | 
| 182 | 
            +
                # @rbs ((String | Symbol) key) ?{ ((String | Symbol)) -> untyped } -> untyped
         | 
| 159 183 | 
             
                def delete!(key, &)
         | 
| 160 184 | 
             
                  table.delete(key.to_sym, &)
         | 
| 161 185 | 
             
                end
         | 
| @@ -170,6 +194,8 @@ module CMDx | |
| 170 194 | 
             
                #   context1 = Context.new(name: "John")
         | 
| 171 195 | 
             
                #   context2 = Context.new(name: "John")
         | 
| 172 196 | 
             
                #   context1 == context2 # => true
         | 
| 197 | 
            +
                #
         | 
| 198 | 
            +
                # @rbs (untyped other) -> bool
         | 
| 173 199 | 
             
                def eql?(other)
         | 
| 174 200 | 
             
                  other.is_a?(self.class) && (to_h == other.to_h)
         | 
| 175 201 | 
             
                end
         | 
| @@ -185,6 +211,8 @@ module CMDx | |
| 185 211 | 
             
                #   context = Context.new(name: "John")
         | 
| 186 212 | 
             
                #   context.key?(:name) # => true
         | 
| 187 213 | 
             
                #   context.key?(:age) # => false
         | 
| 214 | 
            +
                #
         | 
| 215 | 
            +
                # @rbs ((String | Symbol) key) -> bool
         | 
| 188 216 | 
             
                def key?(key)
         | 
| 189 217 | 
             
                  table.key?(key.to_sym)
         | 
| 190 218 | 
             
                end
         | 
| @@ -200,6 +228,8 @@ module CMDx | |
| 200 228 | 
             
                #   context = Context.new(user: {profile: {name: "John"}})
         | 
| 201 229 | 
             
                #   context.dig(:user, :profile, :name) # => "John"
         | 
| 202 230 | 
             
                #   context.dig(:user, :profile, :age) # => nil
         | 
| 231 | 
            +
                #
         | 
| 232 | 
            +
                # @rbs ((String | Symbol) key, *(String | Symbol) keys) -> untyped
         | 
| 203 233 | 
             
                def dig(key, *keys)
         | 
| 204 234 | 
             
                  table.dig(key.to_sym, *keys)
         | 
| 205 235 | 
             
                end
         | 
| @@ -211,6 +241,8 @@ module CMDx | |
| 211 241 | 
             
                # @example
         | 
| 212 242 | 
             
                #   context = Context.new(name: "John", age: 30)
         | 
| 213 243 | 
             
                #   context.to_s # => "name: John, age: 30"
         | 
| 244 | 
            +
                #
         | 
| 245 | 
            +
                # @rbs () -> String
         | 
| 214 246 | 
             
                def to_s
         | 
| 215 247 | 
             
                  Utils::Format.to_str(to_h)
         | 
| 216 248 | 
             
                end
         | 
| @@ -227,6 +259,8 @@ module CMDx | |
| 227 259 | 
             
                # @yield [Object] optional block
         | 
| 228 260 | 
             
                #
         | 
| 229 261 | 
             
                # @return [Object] the result of the method call
         | 
| 262 | 
            +
                #
         | 
| 263 | 
            +
                # @rbs (Symbol method_name, *untyped args, **untyped _kwargs) ?{ () -> untyped } -> untyped
         | 
| 230 264 | 
             
                def method_missing(method_name, *args, **_kwargs, &)
         | 
| 231 265 | 
             
                  fetch(method_name) do
         | 
| 232 266 | 
             
                    store(method_name[0..-2], args.first) if method_name.end_with?("=")
         | 
| @@ -244,6 +278,8 @@ module CMDx | |
| 244 278 | 
             
                #   context = Context.new(name: "John")
         | 
| 245 279 | 
             
                #   context.respond_to?(:name) # => true
         | 
| 246 280 | 
             
                #   context.respond_to?(:age) # => false
         | 
| 281 | 
            +
                #
         | 
| 282 | 
            +
                # @rbs (Symbol method_name, ?bool include_private) -> bool
         | 
| 247 283 | 
             
                def respond_to_missing?(method_name, include_private = false)
         | 
| 248 284 | 
             
                  key?(method_name) || super
         | 
| 249 285 | 
             
                end
         | 
    
        data/lib/cmdx/deprecator.rb
    CHANGED
    
    | @@ -10,6 +10,7 @@ module CMDx | |
| 10 10 |  | 
| 11 11 | 
             
                extend self
         | 
| 12 12 |  | 
| 13 | 
            +
                # @rbs EVAL: Proc
         | 
| 13 14 | 
             
                EVAL = proc do |target, callable|
         | 
| 14 15 | 
             
                  case callable
         | 
| 15 16 | 
             
                  when /raise|log|warn/ then callable
         | 
| @@ -45,6 +46,8 @@ module CMDx | |
| 45 46 | 
             
                #   end
         | 
| 46 47 | 
             
                #
         | 
| 47 48 | 
             
                #   MyTask.new # => [MyTask] DEPRECATED: migrate to a replacement or discontinue use
         | 
| 49 | 
            +
                #
         | 
| 50 | 
            +
                # @rbs (Task task) -> void
         | 
| 48 51 | 
             
                def restrict(task)
         | 
| 49 52 | 
             
                  type = EVAL.call(task, task.class.settings[:deprecate])
         | 
| 50 53 |  | 
    
        data/lib/cmdx/errors.rb
    CHANGED
    
    | @@ -8,11 +8,21 @@ module CMDx | |
| 8 8 |  | 
| 9 9 | 
             
                extend Forwardable
         | 
| 10 10 |  | 
| 11 | 
            +
                # Returns the internal hash of error messages by attribute.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [Hash{Symbol => Set<String>}] Hash mapping attribute names to error message sets
         | 
| 14 | 
            +
                #
         | 
| 15 | 
            +
                # @example
         | 
| 16 | 
            +
                #   errors.messages # => { email: #<Set: ["must be valid", "is required"]> }
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @rbs @messages: Hash[Symbol, Set[String]]
         | 
| 11 19 | 
             
                attr_reader :messages
         | 
| 12 20 |  | 
| 13 21 | 
             
                def_delegators :messages, :empty?
         | 
| 14 22 |  | 
| 15 23 | 
             
                # Initialize a new error collection.
         | 
| 24 | 
            +
                #
         | 
| 25 | 
            +
                # @rbs () -> void
         | 
| 16 26 | 
             
                def initialize
         | 
| 17 27 | 
             
                  @messages = {}
         | 
| 18 28 | 
             
                end
         | 
| @@ -26,6 +36,8 @@ module CMDx | |
| 26 36 | 
             
                #   errors = CMDx::Errors.new
         | 
| 27 37 | 
             
                #   errors.add(:email, "must be valid format")
         | 
| 28 38 | 
             
                #   errors.add(:email, "cannot be blank")
         | 
| 39 | 
            +
                #
         | 
| 40 | 
            +
                # @rbs (Symbol attribute, String message) -> void
         | 
| 29 41 | 
             
                def add(attribute, message)
         | 
| 30 42 | 
             
                  return if message.empty?
         | 
| 31 43 |  | 
| @@ -42,6 +54,8 @@ module CMDx | |
| 42 54 | 
             
                # @example
         | 
| 43 55 | 
             
                #   errors.for?(:email) # => true
         | 
| 44 56 | 
             
                #   errors.for?(:name)  # => false
         | 
| 57 | 
            +
                #
         | 
| 58 | 
            +
                # @rbs (Symbol attribute) -> bool
         | 
| 45 59 | 
             
                def for?(attribute)
         | 
| 46 60 | 
             
                  return false unless messages.key?(attribute)
         | 
| 47 61 |  | 
| @@ -54,6 +68,8 @@ module CMDx | |
| 54 68 | 
             
                #
         | 
| 55 69 | 
             
                # @example
         | 
| 56 70 | 
             
                #   errors.full_messages # => { email: ["email must be valid format", "email cannot be blank"] }
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @rbs () -> Hash[Symbol, Array[String]]
         | 
| 57 73 | 
             
                def full_messages
         | 
| 58 74 | 
             
                  messages.each_with_object({}) do |(attribute, messages), hash|
         | 
| 59 75 | 
             
                    hash[attribute] = messages.map { |message| "#{attribute} #{message}" }
         | 
| @@ -66,6 +82,8 @@ module CMDx | |
| 66 82 | 
             
                #
         | 
| 67 83 | 
             
                # @example
         | 
| 68 84 | 
             
                #   errors.to_h # => { email: ["must be valid format", "cannot be blank"] }
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @rbs () -> Hash[Symbol, Array[String]]
         | 
| 69 87 | 
             
                def to_h
         | 
| 70 88 | 
             
                  messages.transform_values(&:to_a)
         | 
| 71 89 | 
             
                end
         | 
| @@ -78,6 +96,8 @@ module CMDx | |
| 78 96 | 
             
                # @example
         | 
| 79 97 | 
             
                #   errors.to_hash # => { email: ["must be valid format", "cannot be blank"] }
         | 
| 80 98 | 
             
                #   errors.to_hash(true) # => { email: ["email must be valid format", "email cannot be blank"] }
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @rbs (?bool full) -> Hash[Symbol, Array[String]]
         | 
| 81 101 | 
             
                def to_hash(full = false)
         | 
| 82 102 | 
             
                  full ? full_messages : to_h
         | 
| 83 103 | 
             
                end
         | 
| @@ -88,6 +108,8 @@ module CMDx | |
| 88 108 | 
             
                #
         | 
| 89 109 | 
             
                # @example
         | 
| 90 110 | 
             
                #   errors.to_s # => "email must be valid format. email cannot be blank"
         | 
| 111 | 
            +
                #
         | 
| 112 | 
            +
                # @rbs () -> String
         | 
| 91 113 | 
             
                def to_s
         | 
| 92 114 | 
             
                  full_messages.values.flatten.join(". ")
         | 
| 93 115 | 
             
                end
         |