br-utils 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.
- checksums.yaml +7 -0
- data/.gitignore +120 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +348 -0
- data/Rakefile +6 -0
- data/examples/boleto_usage_example.rb +79 -0
- data/examples/cep_usage_example.rb +148 -0
- data/examples/cnh_usage_example.rb +120 -0
- data/examples/cnpj_usage_example.rb +227 -0
- data/examples/cpf_usage_example.rb +237 -0
- data/examples/currency_usage_example.rb +266 -0
- data/examples/date_usage_example.rb +259 -0
- data/examples/email_usage_example.rb +321 -0
- data/examples/legal_nature_usage_example.rb +437 -0
- data/examples/legal_process_usage_example.rb +444 -0
- data/examples/license_plate_usage_example.rb +440 -0
- data/examples/phone_usage_example.rb +595 -0
- data/examples/pis_usage_example.rb +588 -0
- data/examples/renavam_usage_example.rb +499 -0
- data/examples/voter_id_usage_example.rb +573 -0
- data/lib/brazilian-utils/boleto-utils.rb +176 -0
- data/lib/brazilian-utils/cep-utils.rb +330 -0
- data/lib/brazilian-utils/cnh-utils.rb +88 -0
- data/lib/brazilian-utils/cnpj-utils.rb +202 -0
- data/lib/brazilian-utils/cpf-utils.rb +192 -0
- data/lib/brazilian-utils/currency-utils.rb +226 -0
- data/lib/brazilian-utils/data/legal_process_ids.json +38 -0
- data/lib/brazilian-utils/date-utils.rb +244 -0
- data/lib/brazilian-utils/email-utils.rb +54 -0
- data/lib/brazilian-utils/legal-nature-utils.rb +235 -0
- data/lib/brazilian-utils/legal-process-utils.rb +240 -0
- data/lib/brazilian-utils/license-plate-utils.rb +279 -0
- data/lib/brazilian-utils/phone-utils.rb +272 -0
- data/lib/brazilian-utils/pis-utils.rb +151 -0
- data/lib/brazilian-utils/renavam-utils.rb +113 -0
- data/lib/brazilian-utils/voter-id-utils.rb +165 -0
- metadata +123 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module BrazilianUtils
|
|
4
|
+
# Utilities for formatting, validating, and generating Brazilian Legal Process IDs.
|
|
5
|
+
#
|
|
6
|
+
# A legal process ID (Número de Processo Judicial) is a 20-digit code that identifies
|
|
7
|
+
# a legal case in the Brazilian judiciary system. The format is:
|
|
8
|
+
# NNNNNNN-DD.AAAA.J.TR.OOOO
|
|
9
|
+
#
|
|
10
|
+
# Where:
|
|
11
|
+
# - NNNNNNN: Sequential number (7 digits)
|
|
12
|
+
# - DD: Verification digits (2 digits) - checksum
|
|
13
|
+
# - AAAA: Year the process was filed (4 digits)
|
|
14
|
+
# - J: Judicial segment (1 digit) - Orgão
|
|
15
|
+
# - TR: Court (2 digits) - Tribunal
|
|
16
|
+
# - OOOO: Court of origin (4 digits) - Foro
|
|
17
|
+
#
|
|
18
|
+
# This module does not verify if a legal process ID corresponds to a real case;
|
|
19
|
+
# it only validates the format and structure of the ID.
|
|
20
|
+
module LegalProcessUtils
|
|
21
|
+
# Path to the JSON file containing valid tribunal and foro IDs
|
|
22
|
+
DATA_FILE = File.join(File.dirname(__FILE__), 'data', 'legal_process_ids.json')
|
|
23
|
+
|
|
24
|
+
# Removes specific symbols (dots and hyphens) from a legal process ID.
|
|
25
|
+
#
|
|
26
|
+
# This function takes a legal process ID as input and removes all occurrences
|
|
27
|
+
# of the '.' and '-' characters from it.
|
|
28
|
+
#
|
|
29
|
+
# @param legal_process [String] A legal process ID containing symbols to be removed
|
|
30
|
+
#
|
|
31
|
+
# @return [String] The legal process ID string with the specified symbols removed
|
|
32
|
+
#
|
|
33
|
+
# @example
|
|
34
|
+
# remove_symbols("123.45-678.901.234-56.7890")
|
|
35
|
+
# #=> "12345678901234567890"
|
|
36
|
+
#
|
|
37
|
+
# remove_symbols("9876543-21.0987.6.54.3210")
|
|
38
|
+
# #=> "98765432109876543210"
|
|
39
|
+
#
|
|
40
|
+
# remove_symbols("1234567890123456789012345")
|
|
41
|
+
# #=> "1234567890123456789012345"
|
|
42
|
+
def self.remove_symbols(legal_process)
|
|
43
|
+
return '' unless legal_process.is_a?(String)
|
|
44
|
+
|
|
45
|
+
legal_process.gsub('.', '').gsub('-', '')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Formats a legal process ID into the standard format.
|
|
49
|
+
#
|
|
50
|
+
# Takes a 20-digit string and formats it as: NNNNNNN-DD.AAAA.J.TR.OOOO
|
|
51
|
+
#
|
|
52
|
+
# @param legal_process_id [String] A 20-digit string representing the legal process ID
|
|
53
|
+
#
|
|
54
|
+
# @return [String, nil] The formatted legal process ID, or nil if the input is invalid
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# format_legal_process("12345678901234567890")
|
|
58
|
+
# #=> "1234567-89.0123.4.56.7890"
|
|
59
|
+
#
|
|
60
|
+
# format_legal_process("98765432109876543210")
|
|
61
|
+
# #=> "9876543-21.0987.6.54.3210"
|
|
62
|
+
#
|
|
63
|
+
# format_legal_process("123")
|
|
64
|
+
# #=> nil
|
|
65
|
+
def self.format_legal_process(legal_process_id)
|
|
66
|
+
return nil unless legal_process_id.is_a?(String)
|
|
67
|
+
return nil unless legal_process_id =~ /^\d{20}$/
|
|
68
|
+
|
|
69
|
+
# Extract fields: NNNNNNN DD AAAA J TR OOOO
|
|
70
|
+
nnnnnnn = legal_process_id[0, 7]
|
|
71
|
+
dd = legal_process_id[7, 2]
|
|
72
|
+
aaaa = legal_process_id[9, 4]
|
|
73
|
+
j = legal_process_id[13, 1]
|
|
74
|
+
tr = legal_process_id[14, 2]
|
|
75
|
+
oooo = legal_process_id[16, 4]
|
|
76
|
+
|
|
77
|
+
"#{nnnnnnn}-#{dd}.#{aaaa}.#{j}.#{tr}.#{oooo}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Checks if a legal process ID is valid.
|
|
81
|
+
#
|
|
82
|
+
# This function validates:
|
|
83
|
+
# 1. The format (20 digits)
|
|
84
|
+
# 2. The checksum (DD verification digits)
|
|
85
|
+
# 3. The tribunal (TR) and foro (OOOO) combination against the official table
|
|
86
|
+
#
|
|
87
|
+
# This function does not verify if the legal process ID corresponds to a real case;
|
|
88
|
+
# it only validates the format and structure of the ID.
|
|
89
|
+
#
|
|
90
|
+
# @param legal_process_id [String] A digit-only or formatted string representing
|
|
91
|
+
# the legal process ID
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean] Returns true if the legal process ID is valid, false otherwise
|
|
94
|
+
#
|
|
95
|
+
# @example
|
|
96
|
+
# is_valid("68476506020233030000")
|
|
97
|
+
# #=> true
|
|
98
|
+
#
|
|
99
|
+
# is_valid("5180823-36.2023.3.03.0000")
|
|
100
|
+
# #=> true
|
|
101
|
+
#
|
|
102
|
+
# is_valid("123")
|
|
103
|
+
# #=> false
|
|
104
|
+
def self.is_valid(legal_process_id)
|
|
105
|
+
return false unless legal_process_id.is_a?(String)
|
|
106
|
+
|
|
107
|
+
clean_id = remove_symbols(legal_process_id)
|
|
108
|
+
return false unless clean_id =~ /^\d{20}$/
|
|
109
|
+
|
|
110
|
+
# Extract fields
|
|
111
|
+
nnnnnnn = clean_id[0, 7]
|
|
112
|
+
dd = clean_id[7, 2]
|
|
113
|
+
aaaa = clean_id[9, 4]
|
|
114
|
+
j = clean_id[13, 1]
|
|
115
|
+
tr = clean_id[14, 2]
|
|
116
|
+
oooo = clean_id[16, 4]
|
|
117
|
+
|
|
118
|
+
# Validate checksum
|
|
119
|
+
base_for_checksum = nnnnnnn + aaaa + j + tr + oooo
|
|
120
|
+
expected_dd = checksum(base_for_checksum.to_i)
|
|
121
|
+
return false unless dd == expected_dd
|
|
122
|
+
|
|
123
|
+
# Validate tribunal and foro against JSON data
|
|
124
|
+
validate_tribunal_and_foro(j.to_i, tr.to_i, oooo.to_i)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Alias for is_valid to provide Ruby-style naming
|
|
128
|
+
class << self
|
|
129
|
+
alias valid? is_valid
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Generates a random legal process ID.
|
|
133
|
+
#
|
|
134
|
+
# @param year [Integer] The year for the legal process ID (default is current year).
|
|
135
|
+
# The year should not be in the past.
|
|
136
|
+
# @param orgao [Integer] The judicial segment code (1-9) for the legal process ID
|
|
137
|
+
# (default is random)
|
|
138
|
+
#
|
|
139
|
+
# @return [String, nil] A randomly generated legal process ID (20 digits),
|
|
140
|
+
# or nil if arguments are invalid
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# generate(2023, 5)
|
|
144
|
+
# #=> "51659517020235080562" (example, actual value is random)
|
|
145
|
+
#
|
|
146
|
+
# generate()
|
|
147
|
+
# #=> "88031888120233030000" (uses current year and random orgao)
|
|
148
|
+
#
|
|
149
|
+
# generate(2022, 10)
|
|
150
|
+
# #=> nil (year in the past, orgao out of range)
|
|
151
|
+
def self.generate(year = Time.now.year, orgao = rand(1..9))
|
|
152
|
+
return nil if year < Time.now.year
|
|
153
|
+
return nil unless (1..9).include?(orgao)
|
|
154
|
+
|
|
155
|
+
data = load_legal_process_data
|
|
156
|
+
return nil unless data
|
|
157
|
+
|
|
158
|
+
orgao_data = data["orgao_#{orgao}"]
|
|
159
|
+
return nil unless orgao_data
|
|
160
|
+
|
|
161
|
+
# Get random tribunal and foro
|
|
162
|
+
tribunals = orgao_data['id_tribunal']
|
|
163
|
+
foros = orgao_data['id_foro']
|
|
164
|
+
|
|
165
|
+
tr = tribunals[rand(tribunals.length)].to_s.rjust(2, '0')
|
|
166
|
+
oooo = foros[rand(foros.length)].to_s.rjust(4, '0')
|
|
167
|
+
|
|
168
|
+
# Generate random sequential number
|
|
169
|
+
nnnnnnn = rand(0..9999999).to_s.rjust(7, '0')
|
|
170
|
+
|
|
171
|
+
# Calculate checksum
|
|
172
|
+
base_for_checksum = nnnnnnn + year.to_s + orgao.to_s + tr + oooo
|
|
173
|
+
dd = checksum(base_for_checksum.to_i)
|
|
174
|
+
|
|
175
|
+
"#{nnnnnnn}#{dd}#{year}#{orgao}#{tr}#{oooo}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Calculates the checksum (verification digits) for a legal process ID.
|
|
179
|
+
#
|
|
180
|
+
# The checksum is calculated as: 97 - ((basenum * 100) % 97), padded to 2 digits.
|
|
181
|
+
#
|
|
182
|
+
# @param basenum [Integer] The base number for checksum calculation
|
|
183
|
+
# (without the verification digits)
|
|
184
|
+
#
|
|
185
|
+
# @return [String] The checksum value as a 2-digit string
|
|
186
|
+
#
|
|
187
|
+
# @private
|
|
188
|
+
def self.checksum(basenum)
|
|
189
|
+
result = 97 - ((basenum * 100) % 97)
|
|
190
|
+
result.to_s.rjust(2, '0')
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
private_class_method :checksum
|
|
194
|
+
|
|
195
|
+
# Validates if a tribunal and foro combination is valid for a given orgao.
|
|
196
|
+
#
|
|
197
|
+
# @param orgao [Integer] The judicial segment (1-9)
|
|
198
|
+
# @param tribunal [Integer] The court number
|
|
199
|
+
# @param foro [Integer] The court of origin number
|
|
200
|
+
#
|
|
201
|
+
# @return [Boolean] True if the combination is valid, false otherwise
|
|
202
|
+
#
|
|
203
|
+
# @private
|
|
204
|
+
def self.validate_tribunal_and_foro(orgao, tribunal, foro)
|
|
205
|
+
return false unless (1..9).include?(orgao)
|
|
206
|
+
|
|
207
|
+
data = load_legal_process_data
|
|
208
|
+
return false unless data
|
|
209
|
+
|
|
210
|
+
orgao_data = data["orgao_#{orgao}"]
|
|
211
|
+
return false unless orgao_data
|
|
212
|
+
|
|
213
|
+
tribunals = orgao_data['id_tribunal']
|
|
214
|
+
foros = orgao_data['id_foro']
|
|
215
|
+
|
|
216
|
+
tribunals.include?(tribunal) && foros.include?(foro)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private_class_method :validate_tribunal_and_foro
|
|
220
|
+
|
|
221
|
+
# Loads the legal process data from the JSON file.
|
|
222
|
+
#
|
|
223
|
+
# @return [Hash, nil] The parsed JSON data, or nil if the file cannot be loaded
|
|
224
|
+
#
|
|
225
|
+
# @private
|
|
226
|
+
def self.load_legal_process_data
|
|
227
|
+
return @legal_process_data if @legal_process_data
|
|
228
|
+
|
|
229
|
+
if File.exist?(DATA_FILE)
|
|
230
|
+
@legal_process_data = JSON.parse(File.read(DATA_FILE))
|
|
231
|
+
else
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
rescue JSON::ParserError, Errno::ENOENT
|
|
235
|
+
nil
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private_class_method :load_legal_process_data
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
module BrazilianUtils
|
|
2
|
+
# Utilities for formatting, validating, and generating Brazilian license plates.
|
|
3
|
+
#
|
|
4
|
+
# Brazilian license plates come in two formats:
|
|
5
|
+
# - Old format: LLLNNNN (3 letters + 4 numbers) e.g., "ABC-1234"
|
|
6
|
+
# - Mercosul format: LLLNLNN (3 letters + 1 number + 1 letter + 2 numbers) e.g., "ABC1D23"
|
|
7
|
+
#
|
|
8
|
+
# The Mercosul format was introduced in 2018 as part of a standardization
|
|
9
|
+
# effort across Mercosul countries.
|
|
10
|
+
module LicensePlateUtils
|
|
11
|
+
# Pattern for old format license plates (LLLNNNN)
|
|
12
|
+
OLD_FORMAT_PATTERN = /^[A-Za-z]{3}[0-9]{4}$/.freeze
|
|
13
|
+
|
|
14
|
+
# Pattern for Mercosul format license plates (LLLNLNN)
|
|
15
|
+
MERCOSUL_PATTERN = /^[A-Z]{3}\d[A-Z]\d{2}$/.freeze
|
|
16
|
+
|
|
17
|
+
# Converts an old pattern license plate (LLLNNNN) to Mercosul format (LLLNLNN).
|
|
18
|
+
#
|
|
19
|
+
# The conversion replaces the first digit (position 4) with its corresponding letter
|
|
20
|
+
# (0→A, 1→B, 2→C, ..., 9→J).
|
|
21
|
+
#
|
|
22
|
+
# @param license_plate [String] A string representing the old pattern license plate
|
|
23
|
+
#
|
|
24
|
+
# @return [String, nil] The converted Mercosul license plate (LLLNLNN) or nil if invalid
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# convert_to_mercosul("ABC1234")
|
|
28
|
+
# #=> "ABC1C34"
|
|
29
|
+
#
|
|
30
|
+
# convert_to_mercosul("ABC4567")
|
|
31
|
+
# #=> "ABC4F67"
|
|
32
|
+
#
|
|
33
|
+
# convert_to_mercosul("ABC-1234")
|
|
34
|
+
# #=> "ABC1C34"
|
|
35
|
+
#
|
|
36
|
+
# convert_to_mercosul("ABC4*67")
|
|
37
|
+
# #=> nil
|
|
38
|
+
def self.convert_to_mercosul(license_plate)
|
|
39
|
+
return nil unless valid_old_format?(license_plate)
|
|
40
|
+
|
|
41
|
+
clean = remove_symbols(license_plate).upcase
|
|
42
|
+
chars = clean.chars
|
|
43
|
+
|
|
44
|
+
# Convert the 5th character (index 4) - the first digit after the letters
|
|
45
|
+
# 0→A, 1→B, 2→C, etc.
|
|
46
|
+
chars[4] = ('A'.ord + chars[4].to_i).chr
|
|
47
|
+
|
|
48
|
+
chars.join
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Formats a license plate into the correct pattern.
|
|
52
|
+
#
|
|
53
|
+
# This function receives a license plate in any pattern (LLLNNNN or LLLNLNN)
|
|
54
|
+
# and returns a formatted version:
|
|
55
|
+
# - Old format: adds dash (ABC-1234)
|
|
56
|
+
# - Mercosul format: uppercase without dash (ABC1D34)
|
|
57
|
+
#
|
|
58
|
+
# @param license_plate [String] A license plate string
|
|
59
|
+
#
|
|
60
|
+
# @return [String, nil] The formatted license plate string or nil if invalid
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# format_license_plate("ABC1234")
|
|
64
|
+
# #=> "ABC-1234" (old format with dash)
|
|
65
|
+
#
|
|
66
|
+
# format_license_plate("abc1e34")
|
|
67
|
+
# #=> "ABC1E34" (Mercosul format, uppercase)
|
|
68
|
+
#
|
|
69
|
+
# format_license_plate("ABC-1234")
|
|
70
|
+
# #=> "ABC-1234" (already formatted old format)
|
|
71
|
+
#
|
|
72
|
+
# format_license_plate("ABC123")
|
|
73
|
+
# #=> nil (invalid)
|
|
74
|
+
def self.format_license_plate(license_plate)
|
|
75
|
+
return nil unless license_plate.is_a?(String)
|
|
76
|
+
|
|
77
|
+
clean = remove_symbols(license_plate).upcase
|
|
78
|
+
|
|
79
|
+
if valid_old_format?(clean)
|
|
80
|
+
# Old format: add dash after 3rd character
|
|
81
|
+
"#{clean[0..2]}-#{clean[3..-1]}"
|
|
82
|
+
elsif valid_mercosul?(clean)
|
|
83
|
+
# Mercosul format: just uppercase, no dash
|
|
84
|
+
clean
|
|
85
|
+
else
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Alias for format_license_plate
|
|
91
|
+
class << self
|
|
92
|
+
alias format format_license_plate
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns if a Brazilian license plate number is valid.
|
|
96
|
+
#
|
|
97
|
+
# It does not verify if the plate actually exists, only validates the format.
|
|
98
|
+
#
|
|
99
|
+
# @param license_plate [String] The license plate number to be validated
|
|
100
|
+
# @param type [Symbol, String, nil] :old_format, :mercosul, "old_format", or "mercosul".
|
|
101
|
+
# If not specified, checks for either format.
|
|
102
|
+
#
|
|
103
|
+
# @return [Boolean] True if the plate number is valid, false otherwise
|
|
104
|
+
#
|
|
105
|
+
# @example
|
|
106
|
+
# is_valid("ABC1234")
|
|
107
|
+
# #=> true (old format)
|
|
108
|
+
#
|
|
109
|
+
# is_valid("ABC1D34")
|
|
110
|
+
# #=> true (Mercosul format)
|
|
111
|
+
#
|
|
112
|
+
# is_valid("ABC-1234")
|
|
113
|
+
# #=> true (old format with dash)
|
|
114
|
+
#
|
|
115
|
+
# is_valid("ABC1234", :old_format)
|
|
116
|
+
# #=> true
|
|
117
|
+
#
|
|
118
|
+
# is_valid("ABC1D34", :old_format)
|
|
119
|
+
# #=> false
|
|
120
|
+
#
|
|
121
|
+
# is_valid("ABC1D34", :mercosul)
|
|
122
|
+
# #=> true
|
|
123
|
+
#
|
|
124
|
+
# is_valid("ABCD123")
|
|
125
|
+
# #=> false
|
|
126
|
+
def self.is_valid(license_plate, type = nil)
|
|
127
|
+
return false unless license_plate.is_a?(String)
|
|
128
|
+
|
|
129
|
+
clean = remove_symbols(license_plate)
|
|
130
|
+
|
|
131
|
+
type_str = type.to_s if type
|
|
132
|
+
|
|
133
|
+
case type_str
|
|
134
|
+
when 'old_format'
|
|
135
|
+
valid_old_format?(clean)
|
|
136
|
+
when 'mercosul'
|
|
137
|
+
valid_mercosul?(clean)
|
|
138
|
+
else
|
|
139
|
+
valid_old_format?(clean) || valid_mercosul?(clean)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Alias for is_valid
|
|
144
|
+
class << self
|
|
145
|
+
alias valid? is_valid
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Removes the dash (-) symbol from a license plate string.
|
|
149
|
+
#
|
|
150
|
+
# @param license_plate_number [String] A license plate number containing symbols
|
|
151
|
+
#
|
|
152
|
+
# @return [String] The license plate number with dashes removed
|
|
153
|
+
#
|
|
154
|
+
# @example
|
|
155
|
+
# remove_symbols("ABC-1234")
|
|
156
|
+
# #=> "ABC1234"
|
|
157
|
+
#
|
|
158
|
+
# remove_symbols("abc123")
|
|
159
|
+
# #=> "abc123"
|
|
160
|
+
#
|
|
161
|
+
# remove_symbols("ABCD123")
|
|
162
|
+
# #=> "ABCD123"
|
|
163
|
+
def self.remove_symbols(license_plate_number)
|
|
164
|
+
return '' unless license_plate_number.is_a?(String)
|
|
165
|
+
|
|
166
|
+
license_plate_number.gsub('-', '')
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Returns the format of a license plate.
|
|
170
|
+
#
|
|
171
|
+
# Returns 'LLLNNNN' for the old pattern and 'LLLNLNN' for the Mercosul one.
|
|
172
|
+
#
|
|
173
|
+
# @param license_plate [String] A license plate string without symbols
|
|
174
|
+
#
|
|
175
|
+
# @return [String, nil] The format of the license plate (LLLNNNN, LLLNLNN) or nil if invalid
|
|
176
|
+
#
|
|
177
|
+
# @example
|
|
178
|
+
# get_format("ABC1234")
|
|
179
|
+
# #=> "LLLNNNN"
|
|
180
|
+
#
|
|
181
|
+
# get_format("abc1d23")
|
|
182
|
+
# #=> "LLLNLNN"
|
|
183
|
+
#
|
|
184
|
+
# get_format("ABC-1234")
|
|
185
|
+
# #=> "LLLNNNN" (dash is removed automatically)
|
|
186
|
+
#
|
|
187
|
+
# get_format("ABCD123")
|
|
188
|
+
# #=> nil
|
|
189
|
+
def self.get_format(license_plate)
|
|
190
|
+
return nil unless license_plate.is_a?(String)
|
|
191
|
+
|
|
192
|
+
clean = remove_symbols(license_plate)
|
|
193
|
+
|
|
194
|
+
if valid_old_format?(clean)
|
|
195
|
+
'LLLNNNN'
|
|
196
|
+
elsif valid_mercosul?(clean)
|
|
197
|
+
'LLLNLNN'
|
|
198
|
+
else
|
|
199
|
+
nil
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Generates a valid license plate in the given format.
|
|
204
|
+
#
|
|
205
|
+
# In case no format is provided, it will return a license plate in the Mercosul format.
|
|
206
|
+
#
|
|
207
|
+
# @param format [String] The desired format for the license plate.
|
|
208
|
+
# 'LLLNNNN' for the old pattern or 'LLLNLNN' for the Mercosul one.
|
|
209
|
+
# Default is 'LLLNLNN'
|
|
210
|
+
#
|
|
211
|
+
# @return [String, nil] A randomly generated license plate number or nil if format is invalid
|
|
212
|
+
#
|
|
213
|
+
# @example
|
|
214
|
+
# generate
|
|
215
|
+
# #=> "ABC1D23" (Mercosul format by default)
|
|
216
|
+
#
|
|
217
|
+
# generate('LLLNLNN')
|
|
218
|
+
# #=> "XYZ2E45" (Mercosul format)
|
|
219
|
+
#
|
|
220
|
+
# generate('LLLNNNN')
|
|
221
|
+
# #=> "ABC1234" (old format)
|
|
222
|
+
#
|
|
223
|
+
# generate('invalid')
|
|
224
|
+
# #=> nil
|
|
225
|
+
def self.generate(format = 'LLLNLNN')
|
|
226
|
+
format_upper = format.upcase
|
|
227
|
+
|
|
228
|
+
return nil unless ['LLLNLNN', 'LLLNNNN'].include?(format_upper)
|
|
229
|
+
|
|
230
|
+
generated = ''
|
|
231
|
+
|
|
232
|
+
format_upper.each_char do |char|
|
|
233
|
+
if char == 'L'
|
|
234
|
+
# Generate random uppercase letter
|
|
235
|
+
generated += ('A'..'Z').to_a.sample
|
|
236
|
+
else # char == 'N'
|
|
237
|
+
# Generate random digit
|
|
238
|
+
generated += rand(0..9).to_s
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
generated
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Checks whether a string matches the old format of Brazilian license plate.
|
|
246
|
+
#
|
|
247
|
+
# Pattern: LLLNNNN (3 letters + 4 numbers)
|
|
248
|
+
#
|
|
249
|
+
# @param license_plate [String] The license plate to validate
|
|
250
|
+
#
|
|
251
|
+
# @return [Boolean] True if the plate matches old format, false otherwise
|
|
252
|
+
#
|
|
253
|
+
# @private
|
|
254
|
+
def self.valid_old_format?(license_plate)
|
|
255
|
+
return false unless license_plate.is_a?(String)
|
|
256
|
+
|
|
257
|
+
OLD_FORMAT_PATTERN.match?(license_plate.strip)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private_class_method :valid_old_format?
|
|
261
|
+
|
|
262
|
+
# Checks whether a string matches the Mercosul format of Brazilian license plate.
|
|
263
|
+
#
|
|
264
|
+
# Pattern: LLLNLNN (3 letters + 1 number + 1 letter + 2 numbers)
|
|
265
|
+
#
|
|
266
|
+
# @param license_plate [String] The license plate to validate
|
|
267
|
+
#
|
|
268
|
+
# @return [Boolean] True if the plate matches Mercosul format, false otherwise
|
|
269
|
+
#
|
|
270
|
+
# @private
|
|
271
|
+
def self.valid_mercosul?(license_plate)
|
|
272
|
+
return false unless license_plate.is_a?(String)
|
|
273
|
+
|
|
274
|
+
MERCOSUL_PATTERN.match?(license_plate.upcase.strip)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
private_class_method :valid_mercosul?
|
|
278
|
+
end
|
|
279
|
+
end
|