br-utils 0.1.0 → 0.2.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 +4 -4
- data/.github/workflows/ci.yml +27 -0
- data/README.md +446 -22
- data/lib/brazilian-utils/area-code-utils.rb +60 -0
- data/lib/brazilian-utils/bank-account-utils.rb +82 -0
- data/lib/brazilian-utils/bank-utils.rb +61 -0
- data/lib/brazilian-utils/boleto-utils.rb +139 -0
- data/lib/brazilian-utils/caepf-utils.rb +91 -0
- data/lib/brazilian-utils/cbo-utils.rb +74 -0
- data/lib/brazilian-utils/cei-utils.rb +79 -0
- data/lib/brazilian-utils/cep-utils.rb +34 -1
- data/lib/brazilian-utils/certidao-utils.rb +171 -0
- data/lib/brazilian-utils/cfop-utils.rb +74 -0
- data/lib/brazilian-utils/cnae-utils.rb +104 -0
- data/lib/brazilian-utils/cnh-utils.rb +68 -15
- data/lib/brazilian-utils/cno-utils.rb +76 -0
- data/lib/brazilian-utils/cnpj-utils.rb +126 -13
- data/lib/brazilian-utils/cns-utils.rb +110 -0
- data/lib/brazilian-utils/cpf-utils.rb +14 -1
- data/lib/brazilian-utils/credit-card-utils.rb +47 -0
- data/lib/brazilian-utils/csosn-utils.rb +55 -0
- data/lib/brazilian-utils/cst-utils.rb +103 -0
- data/lib/brazilian-utils/currency-utils.rb +347 -226
- data/lib/brazilian-utils/data/area_codes.json +676 -0
- data/lib/brazilian-utils/data/banks.json +357 -0
- data/lib/brazilian-utils/data/cbo.json +1 -0
- data/lib/brazilian-utils/data/cfop.json +1 -0
- data/lib/brazilian-utils/data/cnae.json +1 -0
- data/lib/brazilian-utils/data/csosn.json +42 -0
- data/lib/brazilian-utils/data/cst.json +294 -0
- data/lib/brazilian-utils/data/legal_nature.json +900 -0
- data/lib/brazilian-utils/data/municipalities.json +1 -0
- data/lib/brazilian-utils/data/ncm.json +1 -0
- data/lib/brazilian-utils/data/states.json +218 -0
- data/lib/brazilian-utils/date-utils.rb +504 -244
- data/lib/brazilian-utils/email-utils.rb +20 -9
- data/lib/brazilian-utils/iban-utils.rb +120 -0
- data/lib/brazilian-utils/ie-utils.rb +84 -0
- data/lib/brazilian-utils/legal-nature-utils.rb +238 -235
- data/lib/brazilian-utils/legal-process-utils.rb +35 -5
- data/lib/brazilian-utils/license-plate-utils.rb +24 -4
- data/lib/brazilian-utils/municipality-utils.rb +58 -0
- data/lib/brazilian-utils/ncm-utils.rb +91 -0
- data/lib/brazilian-utils/nfe-key-utils.rb +158 -0
- data/lib/brazilian-utils/number-utils.rb +123 -0
- data/lib/brazilian-utils/passport-utils.rb +65 -0
- data/lib/brazilian-utils/phone-utils.rb +491 -272
- data/lib/brazilian-utils/pis-utils.rb +16 -1
- data/lib/brazilian-utils/pix-key-utils.rb +70 -0
- data/lib/brazilian-utils/pix-payload-utils.rb +253 -0
- data/lib/brazilian-utils/registro-profissional-utils.rb +112 -0
- data/lib/brazilian-utils/renavam-utils.rb +14 -0
- data/lib/brazilian-utils/state-utils.rb +105 -0
- data/lib/brazilian-utils/text-utils.rb +105 -0
- data/lib/brazilian-utils/vin-utils.rb +46 -0
- data/lib/brazilian-utils/voter-id-utils.rb +70 -19
- metadata +54 -2
- data/.travis.yml +0 -5
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
module BrazilianUtils
|
|
2
2
|
module EmailUtils
|
|
3
|
-
# Email validation pattern based on RFC 5322
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# -
|
|
11
|
-
|
|
3
|
+
# Email validation pattern based on RFC 5322/RFC 1035.
|
|
4
|
+
#
|
|
5
|
+
# This is stricter than brutils/python's reference regex
|
|
6
|
+
# (`^(?![.])[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`), which lets
|
|
7
|
+
# through addresses with a leading/trailing/doubled dot in the local
|
|
8
|
+
# part, or a domain label starting/ending with a hyphen. Each dot-joined
|
|
9
|
+
# segment is validated on its own:
|
|
10
|
+
# - Local part: one or more non-empty segments (letters, digits,
|
|
11
|
+
# `._%+-`) joined by single dots - no leading/trailing/consecutive dot
|
|
12
|
+
# - Domain labels: alphanumeric, may contain internal hyphens, but must
|
|
13
|
+
# not start or end with one (RFC 1035 "preferred name syntax")
|
|
14
|
+
# - TLD: at least 2 letters, no digits or hyphens
|
|
15
|
+
EMAIL_PATTERN = %r{
|
|
16
|
+
\A
|
|
17
|
+
[a-zA-Z0-9_%+-]+ (?: \. [a-zA-Z0-9_%+-]+ )* # local part
|
|
18
|
+
@
|
|
19
|
+
(?: [a-zA-Z0-9] (?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])? \. )+ # domain labels
|
|
20
|
+
[a-zA-Z]{2,63} # TLD
|
|
21
|
+
\z
|
|
22
|
+
}x.freeze
|
|
12
23
|
|
|
13
24
|
# Checks if a string corresponds to a valid email address.
|
|
14
25
|
#
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module BrazilianUtils
|
|
2
|
+
# Utilities for the Brazilian IBAN: `BR` + 2 ISO 7064 MOD 97-10 check
|
|
3
|
+
# digits + an 8-digit bank ISPB + a 5-digit branch + a 10-digit account +
|
|
4
|
+
# a 1-letter account type + a 1-character owner indicator (29 characters
|
|
5
|
+
# total).
|
|
6
|
+
module IBANUtils
|
|
7
|
+
LENGTH = 29
|
|
8
|
+
|
|
9
|
+
# @private
|
|
10
|
+
def self.apply_mask(chars, group_size)
|
|
11
|
+
chars.chars.each_slice(group_size).map(&:join).join(' ')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private_class_method :apply_mask
|
|
15
|
+
|
|
16
|
+
# Removes IBAN formatting, keeps letters and digits upper-cased, capped
|
|
17
|
+
# to 29 characters.
|
|
18
|
+
#
|
|
19
|
+
# @param value [String, Integer]
|
|
20
|
+
# @return [String]
|
|
21
|
+
def self.parse(value)
|
|
22
|
+
return '' unless value.is_a?(String) || value.is_a?(Integer)
|
|
23
|
+
|
|
24
|
+
value.to_s.gsub(/[^a-zA-Z0-9]/, '').upcase[0, LENGTH]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Formats an IBAN in the ISO 13616 print grouping: blocks of 4
|
|
28
|
+
# characters separated by spaces, upper-cased. Does not validate (use
|
|
29
|
+
# {is_valid}).
|
|
30
|
+
#
|
|
31
|
+
# @param value [String]
|
|
32
|
+
# @return [String]
|
|
33
|
+
def self.format(value)
|
|
34
|
+
cleaned = parse(value)
|
|
35
|
+
return '' if cleaned.empty?
|
|
36
|
+
|
|
37
|
+
apply_mask(cleaned, 4)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Normalizes an IBAN candidate, honoring the printed 4-character
|
|
41
|
+
# grouping (a single whitespace, `.`, `-` or `/` between groups; a
|
|
42
|
+
# separator inside a group is rejected).
|
|
43
|
+
#
|
|
44
|
+
# @return [String, nil]
|
|
45
|
+
#
|
|
46
|
+
# @private
|
|
47
|
+
def self.normalize(value)
|
|
48
|
+
return nil unless value.is_a?(String)
|
|
49
|
+
|
|
50
|
+
raw = value.strip
|
|
51
|
+
return nil if raw.empty?
|
|
52
|
+
|
|
53
|
+
alnum_count = 0
|
|
54
|
+
raw.each_char do |ch|
|
|
55
|
+
if ch.match?(/[A-Za-z0-9]/)
|
|
56
|
+
alnum_count += 1
|
|
57
|
+
elsif ch.match?(%r{[\s.\-/]})
|
|
58
|
+
return nil unless alnum_count.positive? && (alnum_count % 4).zero?
|
|
59
|
+
else
|
|
60
|
+
return nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
compact = raw.gsub(%r{[\s.\-/]}, '').upcase
|
|
65
|
+
compact.match?(/\A[A-Z0-9]{#{LENGTH}}\z/) ? compact : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private_class_method :normalize
|
|
69
|
+
|
|
70
|
+
# @private
|
|
71
|
+
def self.checksum_valid?(compact)
|
|
72
|
+
rearranged = compact[4..-1] + compact[0, 4]
|
|
73
|
+
numeric = rearranged.chars.map { |c| c.match?(/[A-Z]/) ? (c.ord - 55).to_s : c }.join
|
|
74
|
+
numeric.to_i % 97 == 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private_class_method :checksum_valid?
|
|
78
|
+
|
|
79
|
+
# Validates a Brazilian IBAN; any other country is invalid.
|
|
80
|
+
#
|
|
81
|
+
# @param value [String]
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def self.is_valid(value)
|
|
84
|
+
compact = normalize(value)
|
|
85
|
+
return false unless compact
|
|
86
|
+
return false unless compact.start_with?('BR')
|
|
87
|
+
return false unless compact[2, 2].match?(/\A\d{2}\z/)
|
|
88
|
+
return false unless compact[4, 8].match?(/\A\d{8}\z/)
|
|
89
|
+
return false unless compact[12, 5].match?(/\A\d{5}\z/)
|
|
90
|
+
return false unless compact[17, 10].match?(/\A\d{10}\z/)
|
|
91
|
+
return false unless compact[27, 1].match?(/\A[A-Z]\z/)
|
|
92
|
+
return false unless compact[28, 1].match?(/\A[1-9A-Z]\z/)
|
|
93
|
+
|
|
94
|
+
checksum_valid?(compact)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class << self
|
|
98
|
+
alias valid? is_valid
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Parses a Brazilian IBAN into its fields.
|
|
102
|
+
#
|
|
103
|
+
# @param value [String]
|
|
104
|
+
# @return [Hash, nil] `nil` whenever {is_valid} would return false.
|
|
105
|
+
def self.get_info(value)
|
|
106
|
+
return nil unless is_valid(value)
|
|
107
|
+
|
|
108
|
+
compact = normalize(value)
|
|
109
|
+
{
|
|
110
|
+
countryCode: compact[0, 2],
|
|
111
|
+
checkDigits: compact[2, 2],
|
|
112
|
+
bankIspb: compact[4, 8],
|
|
113
|
+
branch: compact[12, 5],
|
|
114
|
+
account: compact[17, 10],
|
|
115
|
+
accountType: compact[27, 1],
|
|
116
|
+
owner: compact[28, 1]
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module BrazilianUtils
|
|
2
|
+
# Utilities for the Inscrição Estadual (IE), the state-level tax
|
|
3
|
+
# registration number.
|
|
4
|
+
#
|
|
5
|
+
# @note This is deliberately a *structural-only* check: each of the 27
|
|
6
|
+
# Brazilian states (and the Distrito Federal) defines its own SINTEGRA
|
|
7
|
+
# check-digit algorithm for its IE, and the contract for this function
|
|
8
|
+
# does not supply a single test case exercising any of them. Without a
|
|
9
|
+
# verifiable reference, computing (and possibly getting wrong) 27
|
|
10
|
+
# different check-digit algorithms would be worse than only validating
|
|
11
|
+
# the one thing that is unambiguous per the contract: the expected
|
|
12
|
+
# digit count for each state. This mirrors the reference (Go)
|
|
13
|
+
# implementation, which takes the same structural-only approach for the
|
|
14
|
+
# same reason.
|
|
15
|
+
module IEUtils
|
|
16
|
+
# State (UF) -> accepted digit-count(s) after stripping mask
|
|
17
|
+
# characters ({space, '.', '-', '/'}).
|
|
18
|
+
DIGIT_COUNTS = {
|
|
19
|
+
'AC' => [13],
|
|
20
|
+
'AL' => [9],
|
|
21
|
+
'AM' => [9],
|
|
22
|
+
'AP' => [9],
|
|
23
|
+
'BA' => [8, 9],
|
|
24
|
+
'CE' => [9],
|
|
25
|
+
'DF' => [13],
|
|
26
|
+
'ES' => [9],
|
|
27
|
+
'GO' => [9],
|
|
28
|
+
'MA' => [9],
|
|
29
|
+
'MG' => [13],
|
|
30
|
+
'MS' => [9],
|
|
31
|
+
'MT' => [11],
|
|
32
|
+
'PA' => [9],
|
|
33
|
+
'PB' => [9],
|
|
34
|
+
'PE' => [9, 14],
|
|
35
|
+
'PI' => [9],
|
|
36
|
+
'PR' => [10],
|
|
37
|
+
'RJ' => [8],
|
|
38
|
+
'RN' => [9, 10],
|
|
39
|
+
'RO' => [9, 14],
|
|
40
|
+
'RR' => [9],
|
|
41
|
+
'RS' => [10],
|
|
42
|
+
'SC' => [9],
|
|
43
|
+
'SE' => [9],
|
|
44
|
+
'SP' => [12],
|
|
45
|
+
'TO' => [9, 11]
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
# Checks the *structure* of a state Inscrição Estadual: whether its
|
|
49
|
+
# cleaned length matches one of the digit counts accepted by the given
|
|
50
|
+
# UF. This never validates a per-state SINTEGRA check digit.
|
|
51
|
+
#
|
|
52
|
+
# @param value [String, Integer] The IE, bare or separated by any mix
|
|
53
|
+
# of space, `.`, `-` or `/`. For São Paulo, a value starting with
|
|
54
|
+
# `P`/`p` (a produtor rural registration) is accepted when its
|
|
55
|
+
# cleaned length is exactly 13.
|
|
56
|
+
# @param state [String] The two-letter UF code (case-insensitive).
|
|
57
|
+
# @return [Boolean] false for an unknown UF or empty/invalid input.
|
|
58
|
+
def self.is_valid(value, state)
|
|
59
|
+
return false unless value.is_a?(String) || value.is_a?(Integer)
|
|
60
|
+
|
|
61
|
+
uf = state.to_s.strip.upcase
|
|
62
|
+
counts = DIGIT_COUNTS[uf]
|
|
63
|
+
return false unless counts
|
|
64
|
+
|
|
65
|
+
raw = value.to_s.strip
|
|
66
|
+
return false if raw.empty?
|
|
67
|
+
|
|
68
|
+
cleaned = raw.gsub(%r{[\s.\-/]}, '')
|
|
69
|
+
return false if cleaned.empty?
|
|
70
|
+
|
|
71
|
+
if uf == 'SP' && cleaned.start_with?('P', 'p')
|
|
72
|
+
return cleaned.length == 13
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return false unless cleaned.match?(/\A\d+\z/)
|
|
76
|
+
|
|
77
|
+
counts.include?(cleaned.length)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class << self
|
|
81
|
+
alias valid? is_valid
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|