Module: Clack::Validators
- Defined in:
- lib/clack/validators.rb
Overview
Built-in validators for common validation patterns.
Use these with the validate: option on prompts.
Validation procs can perform any operation including slow I/O (database lookups, API calls, etc.) - they simply block until complete.
Constant Summary collapse
- SHORTCUTS =
Built-ins reachable by a bare Symbol (+validate: :email+) or as a Hash key with a custom message (+validate: "Bad email"+). Each takes only an optional message.
Shortcuts are typed against the prompt's value:
:future_dateand:past_dateexpect a Date (thedateprompt);:path_exists,:directory_exists, and:file_exists_warningexpect a path String (+text+ orpath);:required,:email,:url, and:integerwork on any string-ish value. %i[ required email url integer path_exists directory_exists future_date past_date file_exists_warning ].freeze
Class Method Summary collapse
-
.as_warning(validator) ⇒ Proc
Convert any validator to return a warning instead of an error.
-
.combine(*validators) ⇒ Proc
Combines multiple validators.
-
.date_range(min:, max:, message: nil) ⇒ Proc
Validates that the date is within a given range.
-
.directory_exists(message = "Directory does not exist") ⇒ Proc
Validates directory path exists.
-
.email(message = "Must be a valid email address") ⇒ Proc
Common email format validator.
-
.file_exists_warning(message = "File already exists. Overwrite?") ⇒ Proc
Warning if file exists.
-
.format(pattern, message = "Invalid format") ⇒ Proc
Validates that input matches a regular expression.
-
.future_date(message = "Date must be in the future") ⇒ Proc
Validates that the date is strictly after today.
-
.in_range(range, message = nil) ⇒ Proc
Validates that input is within a numeric range.
-
.integer(message = "Must be a number") ⇒ Proc
Validates that input is a valid integer.
-
.max_length(length, message = nil) ⇒ Proc
Validates maximum length.
-
.min_length(length, message = nil) ⇒ Proc
Validates minimum length.
-
.one_of(allowed, message = nil) ⇒ Proc
Validates that input is in a list of allowed values.
-
.past_date(message = "Date must be in the past") ⇒ Proc
Validates that the date is strictly before today.
-
.path_exists(message = "Path does not exist") ⇒ Proc
Validates file path exists.
-
.required(message = "This field is required") ⇒ Proc
Validates that the input is not empty.
-
.resolve(validator) ⇒ #call?
Normalize the
validate:option into a callable, or nil. -
.url(message = "Must be a valid URL") ⇒ Proc
Common URL format validator.
Class Method Details
.as_warning(validator) ⇒ Proc
Convert any validator to return a warning instead of an error.
Warnings allow the user to proceed with confirmation.
The argument is normalized with resolve, so as_warning(:email) and
+as_warning(/\A[a-z]+\z/)+ work.
276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/clack/validators.rb', line 276 def as_warning(validator) raise ArgumentError, "as_warning needs a validator, got #{validator.inspect}" unless validator resolved = resolve(validator) lambda do |value| result = resolved.call(value) next if result.nil? result.is_a?(Clack::Warning) ? result : Clack::Warning.new(result) end end |
.combine(*validators) ⇒ Proc
Combines multiple validators. Returns the first error or warning, or nil if all pass.
Each argument is normalized with resolve, so Regexps, Symbols, nested Arrays,
and Hashes are accepted alongside procs. nil or false entries are ignored.
178 179 180 181 |
# File 'lib/clack/validators.rb', line 178 def combine(*validators) resolved = validators.map { |validator| resolve(validator) }.compact ->(value) { first_failing_validation(resolved, value) } end |
.date_range(min:, max:, message: nil) ⇒ Proc
Validates that the date is within a given range.
239 240 241 242 |
# File 'lib/clack/validators.rb', line 239 def date_range(min:, max:, message: nil) msg = || "Date must be between #{min} and #{max}" ->(date) { msg unless (min..max).cover?(date) } end |
.directory_exists(message = "Directory does not exist") ⇒ Proc
Validates directory path exists.
211 212 213 |
# File 'lib/clack/validators.rb', line 211 def directory_exists( = "Directory does not exist") ->(value) { unless File.directory?(value.to_s) } end |
.email(message = "Must be a valid email address") ⇒ Proc
Common email format validator.
187 188 189 |
# File 'lib/clack/validators.rb', line 187 def email( = "Must be a valid email address") format(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/, ) end |
.file_exists_warning(message = "File already exists. Overwrite?") ⇒ Proc
Warning if file exists. Allows user to confirm overwrite.
251 252 253 |
# File 'lib/clack/validators.rb', line 251 def file_exists_warning( = "File already exists. Overwrite?") ->(value) { Clack::Warning.new() if File.exist?(value.to_s) } end |
.format(pattern, message = "Invalid format") ⇒ Proc
Validates that input matches a regular expression.
132 133 134 |
# File 'lib/clack/validators.rb', line 132 def format(pattern, = "Invalid format") ->(value) { unless pattern.match?(value.to_s) } end |
.future_date(message = "Date must be in the future") ⇒ Proc
Validates that the date is strictly after today. Today itself is not considered "future" and will fail validation.
220 221 222 |
# File 'lib/clack/validators.rb', line 220 def future_date( = "Date must be in the future") ->(date) { if date <= Date.today } end |
.in_range(range, message = nil) ⇒ Proc
Validates that input is within a numeric range. Note: Parses value as integer for comparison.
160 161 162 163 164 165 166 |
# File 'lib/clack/validators.rb', line 160 def in_range(range, = nil) msg = || "Must be between #{range.first} and #{range.last}" lambda do |value| int_val = value.to_s.to_i msg unless range.cover?(int_val) && value.to_s.match?(/\A-?\d+\z/) end end |
.integer(message = "Must be a number") ⇒ Proc
Validates that input is a valid integer.
150 151 152 |
# File 'lib/clack/validators.rb', line 150 def integer( = "Must be a number") ->(value) { unless value.to_s.match?(/\A-?\d+\z/) } end |
.max_length(length, message = nil) ⇒ Proc
Validates maximum length.
122 123 124 125 |
# File 'lib/clack/validators.rb', line 122 def max_length(length, = nil) msg = || "Must be at most #{length} characters" ->(value) { msg if value.to_s.length > length } end |
.min_length(length, message = nil) ⇒ Proc
Validates minimum length.
112 113 114 115 |
# File 'lib/clack/validators.rb', line 112 def min_length(length, = nil) msg = || "Must be at least #{length} characters" ->(value) { msg if value.to_s.length < length } end |
.one_of(allowed, message = nil) ⇒ Proc
Validates that input is in a list of allowed values.
141 142 143 144 |
# File 'lib/clack/validators.rb', line 141 def one_of(allowed, = nil) msg = || "Must be one of: #{allowed.join(", ")}" ->(value) { msg unless allowed.include?(value) } end |
.past_date(message = "Date must be in the past") ⇒ Proc
Validates that the date is strictly before today. Today itself is not considered "past" and will fail validation.
229 230 231 |
# File 'lib/clack/validators.rb', line 229 def past_date( = "Date must be in the past") ->(date) { if date >= Date.today } end |
.path_exists(message = "Path does not exist") ⇒ Proc
Validates file path exists.
203 204 205 |
# File 'lib/clack/validators.rb', line 203 def path_exists( = "Path does not exist") ->(value) { unless File.exist?(value.to_s) } end |
.required(message = "This field is required") ⇒ Proc
Validates that the input is not empty.
103 104 105 |
# File 'lib/clack/validators.rb', line 103 def required( = "This field is required") ->(value) { if value.to_s.strip.empty? } end |
.resolve(validator) ⇒ #call?
Normalize the validate: option into a callable, or nil.
Accepted shapes:
nilorfalse: no validationRegexp: format with the default "Invalid format" messageSymbol: a zero-argument built-in listed in SHORTCUTSArray: combine of each entry, resolved recursively;nilorfalseentries are droppedHash:RegexporSymbolkeys mapped to a custom message, checked in insertion order. A String message makes the entry an error; a Warning message makes it a soft check (wrapped with as_warning)- anything responding to
#call: returned unchanged
Core::Prompt#initialize calls this, so a bad validator raises when the prompt is built rather than when the user presses Enter.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/clack/validators.rb', line 84 def resolve(validator) case validator when nil, false then nil when Regexp then format(validator) when Symbol then resolve_symbol(validator) when Array then combine(*validator) when Hash then combine(*validator.map { |shape, | (shape, ) }) else return validator if validator.respond_to?(:call) raise ArgumentError, "Validate must be a Regexp, Symbol, Array, Hash, or respond to #call, got #{validator.class}" end end |
.url(message = "Must be a valid URL") ⇒ Proc
Common URL format validator.
195 196 197 |
# File 'lib/clack/validators.rb', line 195 def url( = "Must be a valid URL") format(%r{\Ahttps?://\S+\z}, ) end |