diff --git a/AGENTS.md b/AGENTS.md index 56d53c9..811af73 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -378,3 +378,31 @@ Before finishing any task that adds, changes, or documents errors: 10. A summary table exists with the required columns and ordering, listing only defined failure modes. 11. A rescue-granularity section exists with the four levels above, using real library classes. 12. No docs mention `rescue Exception` or inheriting from `Exception` directly. + +--- + +## Aggregator package re-exports + +Applies to aggregator gems such as `*-utilities` and `br-utilities` that load component packages and expose a unified façade. + +### Shape + +- One re-export file per component under `src//.rb` (e.g. `src/cnpj-utilities/cnpj_fmt.rb`). +- Nest the full sibling module on the façade: `::CnpjFmt = ::CnpjFmt` (same-object assignment only — no wrappers). +- Root shortcuts only for the three (or package-appropriate) **main classes** (e.g. `::CnpjFormatter = CnpjFmt::CnpjFormatter`). +- Options, helpers, errors, and types stay under the nested module — **not** aliased at the `` root. +- Root sibling modules (`CnpjFmt`, `CnpjGen`, `CnpjVal`, …) remain supported unchanged. +- Require the re-export files from the aggregator entrypoint **after** class/module promotion and **after** the façade implementation file. + +### Default singleton + class helpers + +When the façade mirrors a JS default export / Python module-level singleton: + +- Expose a mutable constant `::DEFAULT = new` (UPPERCASE names a constant binding, not an immutable value — do not freeze the instance). +- Add class-method aliases for each façade operation that forward to `DEFAULT` (e.g. `CnpjUtils.format` / `.generate` / `.is_valid`). Prefer these in end-user docs as the quick path. +- Mutating `DEFAULT` affects subsequent class-helper calls; `CnpjUtils.new` (custom) instances stay independent. +- Specs: helper existence, parity with `DEFAULT`, mutability coupling with restore, custom-instance independence. + +### Reference + +Shipped reference: `ruby/packages/cnpj-utilities` (`CnpjUtils::CnpjFmt` nest + `CnpjUtils::CnpjFormatter` shortcut; `DEFAULT` + class helpers). diff --git a/packages/br-utilities/br-utilities.gemspec b/packages/br-utilities/br-utilities.gemspec index baeef6c..addc19d 100644 --- a/packages/br-utilities/br-utilities.gemspec +++ b/packages/br-utilities/br-utilities.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.1' spec.metadata['source_code_uri'] = spec.homepage spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] spec.add_dependency 'cnpj-utilities', '>= 0' spec.add_dependency 'cpf-utilities', '>= 0' diff --git a/packages/cnpj-dv/README.md b/packages/cnpj-dv/README.md index c87b648..23e875d 100644 --- a/packages/cnpj-dv/README.md +++ b/packages/cnpj-dv/README.md @@ -68,10 +68,10 @@ require 'cnpj-dv' check_digits = CnpjDV::CnpjCheckDigits.new('914157320007') -check_digits.first # => '9' -check_digits.second # => '3' -check_digits.both # => '93' -check_digits.cnpj # => '91415732000793' +check_digits.first # => '9' +check_digits.second # => '3' +check_digits.both # => '93' +check_digits.cnpj # => '91415732000793' ``` With alphanumeric CNPJ (new format): @@ -81,10 +81,10 @@ require 'cnpj-dv' check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001') -check_digits.first # => '6' -check_digits.second # => '8' -check_digits.both # => '68' -check_digits.cnpj # => 'MGKGMJ9X000168' +check_digits.first # => '6' +check_digits.second # => '8' +check_digits.both # => '68' +check_digits.cnpj # => 'MGKGMJ9X000168' ``` @@ -178,7 +178,7 @@ rescue CnpjDV::DomainError - **Example:** ```ruby -CnpjDV::CnpjCheckDigits.new(12_345_678_000_100) # raises CnpjDV::TypeMismatchError +CnpjDV::CnpjCheckDigits.new(12_345_678_000_100) # raises CnpjDV::TypeMismatchError ``` - **How to rescue it:** @@ -199,7 +199,7 @@ rescue TypeError - **Example:** ```ruby -CnpjDV::CnpjCheckDigits.new('12345678901') # raises CnpjDV::InvalidLengthError +CnpjDV::CnpjCheckDigits.new('12345678901') # raises CnpjDV::InvalidLengthError ``` - **How to rescue it:** @@ -220,7 +220,7 @@ rescue CnpjDV::DomainError - **Example:** ```ruby -CnpjDV::CnpjCheckDigits.new('000000000001') # raises CnpjDV::ValidationError +CnpjDV::CnpjCheckDigits.new('000000000001') # raises CnpjDV::ValidationError ``` - **How to rescue it:** diff --git a/packages/cnpj-dv/README.pt.md b/packages/cnpj-dv/README.pt.md index 32fe8cb..1b7a090 100644 --- a/packages/cnpj-dv/README.pt.md +++ b/packages/cnpj-dv/README.pt.md @@ -45,10 +45,10 @@ require 'cnpj-dv' check_digits = CnpjDV::CnpjCheckDigits.new('914157320007') -check_digits.first # => '9' -check_digits.second # => '3' -check_digits.both # => '93' -check_digits.cnpj # => '91415732000793' +check_digits.first # => '9' +check_digits.second # => '3' +check_digits.both # => '93' +check_digits.cnpj # => '91415732000793' ``` Com CNPJ alfanumérico (novo formato): @@ -58,10 +58,10 @@ require 'cnpj-dv' check_digits = CnpjDV::CnpjCheckDigits.new('MGKGMJ9X0001') -check_digits.first # => '6' -check_digits.second # => '8' -check_digits.both # => '68' -check_digits.cnpj # => 'MGKGMJ9X000168' +check_digits.first # => '6' +check_digits.second # => '8' +check_digits.both # => '68' +check_digits.cnpj # => 'MGKGMJ9X000168' ``` ## Utilização @@ -149,7 +149,7 @@ rescue CnpjDV::DomainError - **Exemplo:** ```ruby -CnpjDV::CnpjCheckDigits.new(12_345_678_000_100) # levanta CnpjDV::TypeMismatchError +CnpjDV::CnpjCheckDigits.new(12_345_678_000_100) # levanta CnpjDV::TypeMismatchError ``` - **Como resgatar:** @@ -170,7 +170,7 @@ rescue TypeError - **Exemplo:** ```ruby -CnpjDV::CnpjCheckDigits.new('12345678901') # levanta CnpjDV::InvalidLengthError +CnpjDV::CnpjCheckDigits.new('12345678901') # levanta CnpjDV::InvalidLengthError ``` - **Como resgatar:** diff --git a/packages/cnpj-fmt/README.md b/packages/cnpj-fmt/README.md index 5530a79..0ab7047 100644 --- a/packages/cnpj-fmt/README.md +++ b/packages/cnpj-fmt/README.md @@ -70,9 +70,9 @@ require 'cnpj-fmt' cnpj = '03603568000195' -CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" -CnpjFmt.cnpj_fmt(cnpj, hidden: true) # => "03.603.***/****-**" -CnpjFmt.cnpj_fmt( # => "03603568|0001_95" +CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" +CnpjFmt.cnpj_fmt(cnpj, hidden: true) # => "03.603.***/****-**" +CnpjFmt.cnpj_fmt( # => "03603568|0001_95" cnpj, dot_key: '', slash_key: '|', @@ -119,15 +119,15 @@ require 'cnpj-fmt' cnpj = '03603568000195' -CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" -CnpjFmt.cnpj_fmt(cnpj, hidden: true) # masked with defaults -CnpjFmt.cnpj_fmt( # => "03603568|0001_95" +CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" +CnpjFmt.cnpj_fmt(cnpj, hidden: true) # masked with defaults +CnpjFmt.cnpj_fmt( # => "03603568|0001_95" cnpj, dot_key: '', slash_key: '|', dash_key: '_' ) -CnpjFmt.cnpj_fmt(cnpj, { # Hash form +CnpjFmt.cnpj_fmt(cnpj, { # Hash form hidden: true, hidden_key: '#' }) @@ -159,9 +159,9 @@ require 'cnpj-fmt' formatter = CnpjFmt::CnpjFormatter.new(hidden: true) cnpj = '03603568000195' -formatter.format(cnpj) # uses instance masking -formatter.format(cnpj, hidden: false) # this call only: unmasked -formatter.format(cnpj) # back to instance defaults +formatter.format(cnpj) # uses instance masking +formatter.format(cnpj, hidden: false) # this call only: unmasked +formatter.format(cnpj) # back to instance defaults ``` Alphanumeric input and array input: @@ -283,7 +283,7 @@ rescue CnpjFmt::DomainError - **Example:** ```ruby -CnpjFmt::CnpjFormatter.new.format(12_345) # raises CnpjFmt::TypeMismatchError +CnpjFmt::CnpjFormatter.new.format(12_345) # raises CnpjFmt::TypeMismatchError ``` - **How to rescue it:** @@ -307,10 +307,10 @@ rescue TypeError CnpjFmt::CnpjFormatter.new.format( 'short', on_fail: ->(_value, error) { - error # => # (a DomainError) + error # => # (a DomainError) 'invalid' } -) # => "invalid" +) # => "invalid" ``` @@ -358,7 +358,7 @@ rescue ArgumentError - **Example:** ```ruby -CnpjFmt::CnpjFormatterOptions.new(hidden_start: 14) # raises CnpjFmt::OutOfRangeError +CnpjFmt::CnpjFormatterOptions.new(hidden_start: 14) # raises CnpjFmt::OutOfRangeError ``` - **How to rescue it:** @@ -379,7 +379,7 @@ rescue CnpjFmt::DomainError - **Example:** ```ruby -CnpjFmt::CnpjFormatterOptions.new(dot_key: 'å') # raises CnpjFmt::ValidationError +CnpjFmt::CnpjFormatterOptions.new(dot_key: 'å') # raises CnpjFmt::ValidationError ``` - **How to rescue it:** diff --git a/packages/cnpj-fmt/README.pt.md b/packages/cnpj-fmt/README.pt.md index 4fce00c..8c60270 100644 --- a/packages/cnpj-fmt/README.pt.md +++ b/packages/cnpj-fmt/README.pt.md @@ -57,9 +57,9 @@ require 'cnpj-fmt' cnpj = '03603568000195' -CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" -CnpjFmt.cnpj_fmt(cnpj, hidden: true) # => "03.603.***/****-**" -CnpjFmt.cnpj_fmt( # => "03603568|0001_95" +CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" +CnpjFmt.cnpj_fmt(cnpj, hidden: true) # => "03.603.***/****-**" +CnpjFmt.cnpj_fmt( # => "03603568|0001_95" cnpj, dot_key: '', slash_key: '|', @@ -106,15 +106,15 @@ require 'cnpj-fmt' cnpj = '03603568000195' -CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" +CnpjFmt.cnpj_fmt(cnpj) # => "03.603.568/0001-95" CnpjFmt.cnpj_fmt(cnpj, hidden: true) # mascarado com padrões -CnpjFmt.cnpj_fmt( # => "03603568|0001_95" +CnpjFmt.cnpj_fmt( # => "03603568|0001_95" cnpj, dot_key: '', slash_key: '|', dash_key: '_' ) -CnpjFmt.cnpj_fmt(cnpj, { # forma com Hash +CnpjFmt.cnpj_fmt(cnpj, { # forma com Hash hidden: true, hidden_key: '#' }) @@ -146,9 +146,9 @@ require 'cnpj-fmt' formatter = CnpjFmt::CnpjFormatter.new(hidden: true) cnpj = '03603568000195' -formatter.format(cnpj) # usa mascaramento da instância -formatter.format(cnpj, hidden: false) # só nesta chamada: sem máscara -formatter.format(cnpj) # volta aos padrões da instância +formatter.format(cnpj) # usa mascaramento da instância +formatter.format(cnpj, hidden: false) # só nesta chamada: sem máscara +formatter.format(cnpj) # volta aos padrões da instância ``` Entrada alfanumérica e array: @@ -159,7 +159,7 @@ require 'cnpj-fmt' formatter = CnpjFmt::CnpjFormatter.new formatter.format('RK0CMT3W000100') # => "RK.0CM.T3W/0001-00" -formatter.format([ # => "RK.0CM.T3W/0001-00" +formatter.format([ # => "RK.0CM.T3W/0001-00" 'RK', '0CM', 'T3W', @@ -270,7 +270,7 @@ rescue CnpjFmt::DomainError - **Exemplo:** ```ruby -CnpjFmt::CnpjFormatter.new.format(12_345) # levanta CnpjFmt::TypeMismatchError +CnpjFmt::CnpjFormatter.new.format(12_345) # levanta CnpjFmt::TypeMismatchError ``` - **Como resgatar:** @@ -294,10 +294,10 @@ rescue TypeError CnpjFmt::CnpjFormatter.new.format( 'short', on_fail: ->(_value, error) { - error # => # (um DomainError) + error # => # (um DomainError) 'invalid' } -) # => "invalid" +) # => "invalid" ``` - **Como resgatar:** Trate dentro do `on_fail` (caso típico), ou resgate se você o reerguer: @@ -344,7 +344,7 @@ rescue ArgumentError - **Exemplo:** ```ruby -CnpjFmt::CnpjFormatterOptions.new(hidden_start: 14) # levanta CnpjFmt::OutOfRangeError +CnpjFmt::CnpjFormatterOptions.new(hidden_start: 14) # levanta CnpjFmt::OutOfRangeError ``` - **Como resgatar:** @@ -365,7 +365,7 @@ rescue CnpjFmt::DomainError - **Exemplo:** ```ruby -CnpjFmt::CnpjFormatterOptions.new(dot_key: 'å') # levanta CnpjFmt::ValidationError +CnpjFmt::CnpjFormatterOptions.new(dot_key: 'å') # levanta CnpjFmt::ValidationError ``` - **Como resgatar:** diff --git a/packages/cnpj-gen/README.md b/packages/cnpj-gen/README.md index 7e9c1ff..9149955 100644 --- a/packages/cnpj-gen/README.md +++ b/packages/cnpj-gen/README.md @@ -56,12 +56,12 @@ require 'cnpj-gen' ```ruby require 'cnpj-gen' -CnpjGen.cnpj_gen # => e.g. "AB123CDE000155" (14-char alphanumeric) +CnpjGen.cnpj_gen # => e.g. "AB123CDE000155" (14-char alphanumeric) -CnpjGen.cnpj_gen(format: true) # => e.g. "AB.123.CDE/0001-55" +CnpjGen.cnpj_gen(format: true) # => e.g. "AB.123.CDE/0001-55" -CnpjGen.cnpj_gen(prefix: '45623767') # => e.g. "45623767ABCD96" -CnpjGen.cnpj_gen( # => e.g. "45.623.767/ABCD-96" +CnpjGen.cnpj_gen(prefix: '45623767') # => e.g. "45623767ABCD96" +CnpjGen.cnpj_gen( # => e.g. "45.623.767/ABCD-96" prefix: '45623767', format: true ) @@ -110,9 +110,9 @@ require 'cnpj-gen' generator = CnpjGen::CnpjGenerator.new(type: 'numeric', format: true) -generator.generate # => e.g. "73.008.535/0005-06" +generator.generate # => e.g. "73.008.535/0005-06" generator.generate(prefix: '12345678') # override for this call only -generator.options # current default options (CnpjGen::CnpjGeneratorOptions) +generator.options # current default options (CnpjGen::CnpjGeneratorOptions) ``` - **`initialize(options = nil, **keywords)`**: Optional default options. When `options` is given (a `CnpjGen::CnpjGeneratorOptions` instance or a `Hash`) alone, it determines the default options; a `CnpjGen::CnpjGeneratorOptions` instance is stored by reference (mutating it later affects future `generate` calls that do not pass per-call options), while a `Hash` builds a new instance. When `options` is omitted (`nil`), the default options are built exclusively from the keyword arguments (`format:`, `prefix:`, `type:`). Passing `options` together with any non-`nil` keyword raises `InvalidArgumentCombinationError` instead of silently ignoring the keywords. @@ -126,9 +126,9 @@ require 'cnpj-gen' generator = CnpjGen::CnpjGenerator.new(format: true) -generator.generate # formatted CNPJ -generator.generate(format: false) # this call only: unformatted -generator.generate # formatted again (instance defaults preserved) +generator.generate # formatted CNPJ +generator.generate(format: false) # this call only: unformatted +generator.generate # formatted again (instance defaults preserved) ``` ### `CnpjGen::CnpjGeneratorOptions` (class) @@ -143,11 +143,11 @@ options = CnpjGen::CnpjGeneratorOptions.new( type: 'numeric', format: true ) -options.prefix # => "AB123XYZ" -options.type # => "numeric" -options.format # => true -options.set(format: false) # merge and return self -options.all # => { format: false, prefix: "AB123XYZ", type: "numeric" } +options.prefix # => "AB123XYZ" +options.type # => "numeric" +options.format # => true +options.set(format: false) # merge and return self +options.all # => { format: false, prefix: "AB123XYZ", type: "numeric" } # Resetting a property to its default value requires the literal constant — # a bare `nil` on a setter raises TypeMismatchError: @@ -229,7 +229,7 @@ rescue CnpjGen::DomainError - **Example:** ```ruby -CnpjGen.cnpj_gen(prefix: 123) # raises CnpjGen::TypeMismatchError +CnpjGen.cnpj_gen(prefix: 123) # raises CnpjGen::TypeMismatchError ``` - **How to rescue it:** @@ -276,8 +276,8 @@ rescue ArgumentError - **Example:** ```ruby -CnpjGen.cnpj_gen(prefix: '000000000001') # raises CnpjGen::ValidationError -CnpjGen.cnpj_gen(type: 'invalid') # raises CnpjGen::ValidationError +CnpjGen.cnpj_gen(prefix: '000000000001') # raises CnpjGen::ValidationError +CnpjGen.cnpj_gen(type: 'invalid') # raises CnpjGen::ValidationError ``` - **How to rescue it:** diff --git a/packages/cnpj-gen/README.pt.md b/packages/cnpj-gen/README.pt.md index d0ed602..e1486da 100644 --- a/packages/cnpj-gen/README.pt.md +++ b/packages/cnpj-gen/README.pt.md @@ -41,12 +41,12 @@ require 'cnpj-gen' ```ruby require 'cnpj-gen' -CnpjGen.cnpj_gen # => ex.: "AB123CDE000155" (14 caracteres alfanuméricos) +CnpjGen.cnpj_gen # => ex.: "AB123CDE000155" (14 caracteres alfanuméricos) -CnpjGen.cnpj_gen(format: true) # => ex.: "AB.123.CDE/0001-55" +CnpjGen.cnpj_gen(format: true) # => ex.: "AB.123.CDE/0001-55" -CnpjGen.cnpj_gen(prefix: '45623767') # => ex.: "45623767ABCD96" -CnpjGen.cnpj_gen( # => ex.: "45.623.767/ABCD-96" +CnpjGen.cnpj_gen(prefix: '45623767') # => ex.: "45623767ABCD96" +CnpjGen.cnpj_gen( # => ex.: "45.623.767/ABCD-96" prefix: '45623767', format: true ) @@ -95,9 +95,9 @@ require 'cnpj-gen' generator = CnpjGen::CnpjGenerator.new(type: 'numeric', format: true) -generator.generate # => ex.: "73.008.535/0005-06" +generator.generate # => ex.: "73.008.535/0005-06" generator.generate(prefix: '12345678') # sobrescrita apenas nesta chamada -generator.options # opções padrão atuais (CnpjGen::CnpjGeneratorOptions) +generator.options # opções padrão atuais (CnpjGen::CnpjGeneratorOptions) ``` - **`initialize(options = nil, **keywords)`**: Opções padrão opcionais. Quando `options` é fornecido isoladamente (instância de `CnpjGen::CnpjGeneratorOptions` ou `Hash`), ele determina as opções padrão; uma instância de `CnpjGen::CnpjGeneratorOptions` é armazenada por referência (mutações posteriores afetam futuras chamadas de `generate` que não passarem opções por chamada), enquanto um `Hash` cria uma nova instância. Quando `options` é omitido (`nil`), as opções padrão são construídas exclusivamente a partir dos argumentos nomeados (`format:`, `prefix:`, `type:`). Passar `options` junto com qualquer argumento nomeado não `nil` gera `InvalidArgumentCombinationError`, em vez de ignorar os argumentos nomeados silenciosamente. @@ -111,9 +111,9 @@ require 'cnpj-gen' generator = CnpjGen::CnpjGenerator.new(format: true) -generator.generate # CNPJ formatado -generator.generate(format: false) # somente nesta chamada: sem formato -generator.generate # volta ao padrão da instância +generator.generate # CNPJ formatado +generator.generate(format: false) # somente nesta chamada: sem formato +generator.generate # volta ao padrão da instância ``` ### `CnpjGen::CnpjGeneratorOptions` (classe) @@ -261,8 +261,8 @@ rescue ArgumentError - **Exemplo:** ```ruby -CnpjGen.cnpj_gen(prefix: '000000000001') # levanta CnpjGen::ValidationError -CnpjGen.cnpj_gen(type: 'invalid') # levanta CnpjGen::ValidationError +CnpjGen.cnpj_gen(prefix: '000000000001') # levanta CnpjGen::ValidationError +CnpjGen.cnpj_gen(type: 'invalid') # levanta CnpjGen::ValidationError ``` - **Como resgatar:** diff --git a/packages/cnpj-utilities/CHANGELOG.md b/packages/cnpj-utilities/CHANGELOG.md index e07d83e..291d74d 100644 --- a/packages/cnpj-utilities/CHANGELOG.md +++ b/packages/cnpj-utilities/CHANGELOG.md @@ -1 +1,18 @@ # cnpj-utilities + +## 1.0.0 + +### 🚀 Stable Version Released! + +Unified toolkit to deal with CNPJ (Brazilian legal entity ID): formatting, generation, and validation. Main features: + +- **Unified façade**: `CnpjUtils` delegates `#format`, `#generate`, and `#is_valid` to `cnpj-fmt`, `cnpj-gen`, and `cnpj-val`. +- **Alphanumeric CNPJ**: full support for the [14-character alphanumeric CNPJ](https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2023/julho/cnpj-alfa-numerico); generate with `type` `"numeric"`, `"alphabetic"`, or `"alphanumeric"`. +- **Quick helpers**: `CnpjUtils.format` / `.generate` / `.is_valid` alias mutable `CnpjUtils::DEFAULT`. +- **Two-tier re-exports**: `CnpjUtils::CnpjFormatter` / `CnpjGenerator` / `CnpjValidator` at the façade root; full sibling surface under `CnpjUtils::CnpjFmt` / `CnpjGen` / `CnpjVal`. +- **Configurable components**: constructor and setters accept component instances, `*Options`, `Hash`, or `nil`; accessors expose `formatter`, `generator`, and `validator`. +- **Per-call overrides**: `#format`, `#generate`, and `#is_valid` accept an options `Hash`/instance or keyword overrides (not both). +- **Root siblings**: after `require 'cnpj-utilities'`, `CnpjFmt`, `CnpjGen`, and `CnpjVal` remain loadable (same objects as the nests). +- **Structured errors**: `CnpjUtils::TypeMismatchError` / `InvalidArgumentCombinationError` (+ `CnpjUtils::Error` marker); only `nil` means omitted for settings/options (e.g. `false` raises). + +For detailed usage and API reference, see the [README](./README.md). diff --git a/packages/cnpj-utilities/README.md b/packages/cnpj-utilities/README.md new file mode 100644 index 0000000..694f811 --- /dev/null +++ b/packages/cnpj-utilities/README.md @@ -0,0 +1,467 @@ +![cnpj-utilities for Ruby](https://br-utils.vercel.app/img/cover_cnpj-utils.jpg) + +[![Gem Version](https://img.shields.io/gem/v/cnpj-utilities)](https://rubygems.org/gems/cnpj-utilities) +[![Gem Downloads](https://img.shields.io/gem/dt/cnpj-utilities)](https://rubygems.org/gems/cnpj-utilities) +[![Ruby Version](https://img.shields.io/gem/rv/cnpj-utilities)](https://www.ruby-lang.org/) +[![Test Status](https://img.shields.io/github/actions/workflow/status/LacusSolutions/br-utils-ruby/ci.yml?label=ci/cd)](https://github.com/LacusSolutions/br-utils-ruby/actions) +[![Last Update Date](https://img.shields.io/github/last-commit/LacusSolutions/br-utils-ruby)](https://github.com/LacusSolutions/br-utils-ruby) +[![Project License](https://img.shields.io/github/license/LacusSolutions/br-utils-ruby)](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) + +> 🚀 **Full support for the [new alphanumeric CNPJ format](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).** + +> 🌎 [Acessar documentação em português](./README.pt.md) + +A Ruby toolkit to format, generate, and validate CNPJ (Brazilian Business Tax ID). It wraps [`cnpj-fmt`](https://rubygems.org/gems/cnpj-fmt), [`cnpj-gen`](https://rubygems.org/gems/cnpj-gen), and [`cnpj-val`](https://rubygems.org/gems/cnpj-val) in a single façade class (`CnpjUtils`). + +## Ruby Support + +| ![Ruby 3.1](https://img.shields.io/badge/Ruby-3.1-CC342D?logo=ruby&logoColor=white) | ![Ruby 3.2](https://img.shields.io/badge/Ruby-3.2-CC342D?logo=ruby&logoColor=white) | ![Ruby 3.3](https://img.shields.io/badge/Ruby-3.3-CC342D?logo=ruby&logoColor=white) | ![Ruby 3.4](https://img.shields.io/badge/Ruby-3.4-CC342D?logo=ruby&logoColor=white) | ![Ruby 4.0](https://img.shields.io/badge/Ruby-4.0-CC342D?logo=ruby&logoColor=white) | +| --- | --- | --- | --- | --- | +| Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | + +Requires Ruby **≥ 3.1** (see `required_ruby_version` in the gemspec). + +## Features + +- ✅ **Unified API**: Class helpers `CnpjUtils.format` / `.generate` / `.is_valid` +- ✅ **Two-tier access**: Prefer `CnpjUtils::CnpjFormatter` / `CnpjGenerator` / `CnpjValidator` for the main classes; Options, helpers, and errors live under `CnpjUtils::CnpjFmt` / `CnpjGen` / `CnpjVal` (root siblings `CnpjFmt` / `CnpjGen` / `CnpjVal` still work) +- ✅ **Alphanumeric CNPJ**: Format, generate, and validate 14-character numeric or alphanumeric CNPJ +- ✅ **Reusable instance**: `CnpjUtils` class with optional default settings (formatter, generator, validator options or instances) +- ✅ **Flexible input**: `#format` and `#is_valid` accept a `String` or an `Array` of strings (elements concatenated in order) +- ✅ **Per-call overrides**: Instance defaults plus a per-call options `Hash`/`*Options` instance **or** keyword overrides (not both) +- ✅ **Error handling**: Component errors propagate unchanged; this gem defines `CnpjUtils::TypeMismatchError` and `CnpjUtils::InvalidArgumentCombinationError` for API misuse + +## Installation + +Install the gem directly: + +```bash +gem install cnpj-utilities +``` + +Or add it to your `Gemfile` and run `bundle install`: + +```ruby +gem 'cnpj-utilities' +``` + +This installs **`cnpj-utilities`** together with [`cnpj-fmt`](https://rubygems.org/gems/cnpj-fmt), [`cnpj-gen`](https://rubygems.org/gems/cnpj-gen), and [`cnpj-val`](https://rubygems.org/gems/cnpj-val). You do **not** need separate `gem install` / `gem` lines for the component packages when using **`cnpj-utilities`**. + +## Require + +```ruby +require 'cnpj-utilities' +``` + +## Quick Start + +Basic usage with class helpers (aliases of `CnpjUtils::DEFAULT`): + +```ruby +require 'cnpj-utilities' + +cnpj = '03603568000195' + +CnpjUtils.format(cnpj) # => "03.603.568/0001-95" +CnpjUtils.format(cnpj, hidden: true) # => "03.603.***/****-**" +CnpjUtils.format( # => "03603568|0001_95" + cnpj, + dot_key: '', + slash_key: '|', + dash_key: '_' +) + +CnpjUtils.generate # => e.g. "AB123CDE000155" (14-char alphanumeric) +CnpjUtils.generate(format: true) # => e.g. "AB.123.CDE/0001-55" +CnpjUtils.generate(prefix: '45623767') # => e.g. "45623767000296" +CnpjUtils.generate(type: 'numeric') # => e.g. "65453043000178" (digits only) + +CnpjUtils.is_valid('98765432000198') # => true +CnpjUtils.is_valid('98.765.432/0001-98') # => true +CnpjUtils.is_valid('1QB5UKALPYFP59') # => true (alphanumeric) +CnpjUtils.is_valid('98765432000199') # => false +``` + +## Usage + +You can work in these equivalent ways: + +1. **`CnpjUtils.format` / `.generate` / `.is_valid`** — class helpers for quick one-off calls (forward to `DEFAULT`). +2. **`CnpjUtils::DEFAULT`** — mutable shared singleton (same object the class helpers use). +3. **`CnpjUtils.new`** — configurable instance with shared defaults across format, generate, and validate. +4. **Main classes under `CnpjUtils`** — `CnpjUtils::CnpjFormatter`, `CnpjUtils::CnpjGenerator`, `CnpjUtils::CnpjValidator`. +5. **Nested package modules** — Options, helpers, errors, and types via `CnpjUtils::CnpjFmt` / `CnpjGen` / `CnpjVal` (e.g. `CnpjUtils::CnpjFmt::CnpjFormatterOptions`, `CnpjUtils::CnpjFmt.cnpj_fmt`). +6. **Root sibling modules** (still supported) — `CnpjFmt`, `CnpjGen`, `CnpjVal` unchanged. + +All approaches expose the same options and behavior. For exhaustive option tables and component-specific details, see the README of each [bundled package](#bundled-packages). + +### Formatter options + +When calling `#format(cnpj_input, options = nil, **keywords)`, all options are optional: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `hidden` | `Boolean` | `false` | When `true`, mask characters in `hidden_start`–`hidden_end` with `hidden_key` | +| `hidden_key` | `String` | `'*'` | Character(s) used to replace masked characters | +| `hidden_start` | `Integer` | `5` | Start index (0–13, inclusive) of the range to hide | +| `hidden_end` | `Integer` | `13` | End index (0–13, inclusive) of the range to hide | +| `dot_key` | `String` | `'.'` | Dot delimiter (e.g. in `12.345.678`) | +| `slash_key` | `String` | `'/'` | Slash delimiter (e.g. before branch `…/0001-90`) | +| `dash_key` | `String` | `'-'` | Dash delimiter (e.g. before check digits `…-90`) | +| `escape` | `Boolean` | `false` | When `true`, escape HTML special characters in the result | +| `encode` | `Boolean` | `false` | When `true`, URL-encode the result (similar to JavaScript `encodeURIComponent`) | +| `on_fail` | `Proc` / callable | returns `''` | Callback when sanitized input length ≠ 14; return value is used as result | + +### Generator options + +When calling `#generate(options = nil, **keywords)`, all options are optional: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `format` | `Boolean` | `false` | When `true`, return the generated CNPJ in standard format (`00.000.000/0000-00`) | +| `prefix` | `String` | `''` | Partial start string (0–12 alphanumeric chars). Missing characters are generated and check digits computed. | +| `type` | `String` | `'alphanumeric'` | Character set for the randomly generated part: `'numeric'`, `'alphabetic'`, or `'alphanumeric'`. **Check digits are always numeric.** | + +Prefix rules: base ID (first 8 chars) and branch ID (chars 9–12) cannot be all zeros; 12 repeated digits (e.g. `111111111111`) are also not allowed. + +### Validator options + +When calling `#is_valid(cnpj_input, options = nil, **keywords)`, all options are optional: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `case_sensitive` | `Boolean` | `true` | When `false`, lowercase letters are accepted for alphanumeric CNPJ (input is uppercased before validation). | +| `type` | `String` | `'alphanumeric'` | `'numeric'`: only digits (0–9); `'alphanumeric'`: digits and letters (0–9, A–Z). | + +### Class helpers (`CnpjUtils.format` / `.generate` / `.is_valid`) + +These class methods are aliases of the same methods on `CnpjUtils::DEFAULT`. Prefer them for one-off calls: + +```ruby +CnpjUtils.format('03603568000195') +CnpjUtils.generate(type: 'numeric') +CnpjUtils.is_valid('98765432000198') +``` + +### `CnpjUtils::DEFAULT` (default instance) + +`CnpjUtils::DEFAULT` is the pre-built, **mutable** singleton behind the class helpers (parity with the JS default export / Python `cnpj_utils`). Mutating it affects subsequent `CnpjUtils.format` / `.generate` / `.is_valid` calls; custom `CnpjUtils.new` instances stay independent: + +```ruby +CnpjUtils::DEFAULT.formatter = { slash_key: '|' } +CnpjUtils.format('01ABC234000X56') # => "01.ABC.234|000X-56" + +custom = CnpjUtils.new +custom.format('01ABC234000X56') # => "01.ABC.234/000X-56" (unaffected) +``` + +Instance methods on `DEFAULT` (and any `CnpjUtils` instance): + +- **`#format(cnpj_input, options = nil, **keywords)`**: Formats a CNPJ string or array of strings. Delegates to the internal formatter. Input must be 14 alphanumeric characters (after sanitization); otherwise `on_fail` is used. +- **`#generate(options = nil, **keywords)`**: Generates a valid CNPJ. Delegates to the internal generator. +- **`#is_valid(cnpj_input, options = nil, **keywords)`**: Returns `true` if the CNPJ is valid. Delegates to the internal validator. + +### `CnpjUtils` (class) + +For custom default formatter, generator, or validator, create your own instance: + +```ruby +require 'cnpj-utilities' + +utils = CnpjUtils.new( + formatter: { hidden: true, hidden_key: '#' }, + generator: { type: 'numeric', format: true }, + validator: { type: 'numeric', case_sensitive: false } +) + +utils.format('RK0CMT3W000100') # => "RK.0CM.###/####-##" +utils.generate # => e.g. "73.008.535/0005-06" +utils.is_valid('98.765.432/0001-98') # => true + +# Access or replace internal instances +utils.formatter # => CnpjFmt::CnpjFormatter +utils.generator # => CnpjGen::CnpjGenerator +utils.validator # => CnpjVal::CnpjValidator +``` + +- **`CnpjUtils.new(settings = nil, **keywords)`**: Optional settings. Pass either a settings `Hash` with `:formatter`, `:generator`, and/or `:validator` keys, **or** the same keys as keyword arguments — not both (passing both raises `CnpjUtils::InvalidArgumentCombinationError`). Each value may be a component instance, a `*Options` instance (stored by reference — mutating it later affects subsequent calls with no per-call override), a plain options `Hash`, or omitted/`nil` for defaults. +- **`#format(cnpj_input, options = nil, **keywords)`**: Same as the default instance; per-call options override the formatter’s defaults for that call only. Pass either an options `Hash`/`CnpjFmt::CnpjFormatterOptions` **or** keyword overrides — not both. +- **`#generate(options = nil, **keywords)`**: Same as the default instance; per-call options override the generator’s defaults. Pass either an options `Hash`/`CnpjGen::CnpjGeneratorOptions` **or** keyword overrides — not both. +- **`#is_valid(cnpj_input, options = nil, **keywords)`**: Same as the default instance; per-call options override the validator’s defaults. Pass either an options `Hash`/`CnpjVal::CnpjValidatorOptions` **or** keyword overrides — not both. +- **`#formatter`**, **`#generator`**, **`#validator`**: Accessors (getters and setters) for the internal components. Setters accept the same shapes as the constructor. To change a single option without replacing the instance, mutate the component’s options (e.g. `utils.formatter.options.hidden = true`). + +Instance defaults and per-call overrides: + +```ruby +require 'cnpj-utilities' + +utils = CnpjUtils.new( + formatter: { hidden: true, hidden_key: '#' }, + generator: { format: true }, + validator: { type: 'numeric' } +) + +cnpj = '03603568000195' + +utils.format(cnpj) # masked (instance formatter defaults) +utils.format(cnpj, hidden: false) # this call only: unmasked +utils.generate(format: false) # this call only: compact output +utils.is_valid('1QB5UKALPYFP59') # => false (instance validator is numeric-only) +utils.is_valid( # => true for this call + '1QB5UKALPYFP59', + type: 'alphanumeric' +) +``` + +Options can also be passed as a `Hash` (or options instance) on each method — without keyword overrides: + +```ruby +utils.format(cnpj, { slash_key: '|' }) +utils.generate({ prefix: '12345', type: 'numeric' }) +utils.is_valid('1QB5UKALPYFP59', { case_sensitive: false }) +``` + +### Using component classes and nested modules + +Preferred paths after `require 'cnpj-utilities'`: + +```ruby +require 'cnpj-utilities' + +# Main classes at the façade root +formatter = CnpjUtils::CnpjFormatter.new(hidden: true) +generator = CnpjUtils::CnpjGenerator.new(type: 'numeric') +validator = CnpjUtils::CnpjValidator.new + +formatter.format('AB123XYZ000123') # => "AB.123.***/****-**" + +# Options, helpers, and errors under nested package modules +options = CnpjUtils::CnpjFmt::CnpjFormatterOptions.new(slash_key: '|') +CnpjUtils::CnpjFmt.cnpj_fmt('03603568000195') # => "03.603.568/0001-95" + +begin + CnpjUtils::CnpjFmt.cnpj_fmt(12_345) +rescue CnpjUtils::CnpjFmt::TypeMismatchError + # wrong input type +end +``` + +Root siblings remain supported (same objects as the nests): + +```ruby +CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') # => "01.ABC.234|000X-56" +CnpjGen.cnpj_gen(type: 'numeric') # => e.g. "65453043000178" +CnpjVal.cnpj_val('9JN7MGLJZXIO50') # => true +CnpjFmt::CnpjFormatter.new(hidden: true) +``` + +See [`cnpj-fmt`](../cnpj-fmt/README.md), [`cnpj-gen`](../cnpj-gen/README.md), and [`cnpj-val`](../cnpj-val/README.md) for full option and error details. + +## API + +### Exports + +After `require 'cnpj-utilities'`: + +- **`CnpjUtils`**: Façade class to create a utils instance with optional default formatter, generator, and validator settings. +- **`CnpjUtils.format` / `.generate` / `.is_valid`**: Class helpers that forward to `CnpjUtils::DEFAULT`. +- **`CnpjUtils::DEFAULT`**: Mutable pre-built `CnpjUtils` instance (same object the class helpers use). +- **`CnpjUtils::VERSION`**: Gem version string. +- **Main-class shortcuts**: `CnpjUtils::CnpjFormatter`, `CnpjUtils::CnpjGenerator`, `CnpjUtils::CnpjValidator` (same objects as the sibling classes). +- **Nested package modules**: `CnpjUtils::CnpjFmt`, `CnpjUtils::CnpjGen`, `CnpjUtils::CnpjVal` — full sibling surface (Options, helpers, errors, types). Options/helpers/errors are **not** aliased at the `CnpjUtils` root. +- **Root sibling modules** (still supported): `CnpjFmt`, `CnpjGen`, `CnpjVal` — same objects as the nests. + +### Errors & Exceptions + +`CnpjUtils` defines only API-misuse errors for this gem’s argument rules. Component errors are raised by the bundled packages and propagate unchanged. + +#### Defined by `cnpj-utilities` + +Errors defined by this gem are **API misuse** only (wrong type or invalid argument combination). Every custom error includes the `CnpjUtils::Error` marker module. This gem defines **no** `CnpjUtils::DomainError` and no domain leaves — domain failures come only from the [bundled packages](#propagated-from-bundled-packages) and keep those packages’ namespaces (`CnpjFmt::…`, `CnpjGen::…`, `CnpjVal::…`). + +`rescue CnpjUtils::Error` catches **only** errors this gem raises. It does **not** catch component errors that propagate unchanged. + +##### Summary + +| Class | Inherits from | Category | Trigger condition | +|-------|---------------|----------|-------------------| +| `CnpjUtils::TypeMismatchError` | `CnpjUtils::TypeMismatchError < TypeError < StandardError` (+ `include CnpjUtils::Error`) | API misuse | Non-`nil` `settings` argument to `CnpjUtils.new` is not a `Hash` | +| `CnpjUtils::InvalidArgumentCombinationError` | `CnpjUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include CnpjUtils::Error`) | API misuse | Non-`nil` settings/options `Hash` (or options instance) passed together with any non-`nil` keyword argument | + +##### `CnpjUtils::Error` (marker module) + +- **Inheritance:** module marker mixed into every custom error this gem raises via `include` (not a class). +- **Category:** N/A (rescue target only) — not a failure mode by itself. +- **When it is raised:** Never raised directly; included by every custom error this gem raises. +- **Example:** N/A +- **How to rescue it:** + +```ruby +rescue CnpjUtils::Error + # TypeMismatchError, InvalidArgumentCombinationError from this gem only + # (not CnpjFmt::*, CnpjGen::*, or CnpjVal::* errors) +``` + +##### `CnpjUtils::TypeMismatchError` + +- **Inheritance:** `CnpjUtils::TypeMismatchError < TypeError < StandardError` (includes `CnpjUtils::Error`) +- **Category:** API misuse — the caller passed a value of the wrong type. +- **When it is raised:** Raised when `CnpjUtils.new` receives a non-`nil` `settings` argument that is not a `Hash`. +- **Example:** + +```ruby +CnpjUtils.new('not-a-hash') # raises CnpjUtils::TypeMismatchError +CnpjUtils.new(false) # raises CnpjUtils::TypeMismatchError (false is non-nil) +``` + +- **How to rescue it:** + +```ruby +rescue CnpjUtils::TypeMismatchError + # this gem's type-contract violation + +rescue TypeError + # native type errors, including this gem's TypeMismatchError +``` + +##### `CnpjUtils::InvalidArgumentCombinationError` + +- **Inheritance:** `CnpjUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (includes `CnpjUtils::Error`) +- **Category:** API misuse — the caller mixed mutually exclusive argument patterns. +- **When it is raised:** Raised when `CnpjUtils.new`, `#format`, `#generate`, `#is_valid`, or the class helpers receive both a non-`nil` settings/options `Hash` (or options instance) and any non-`nil` keyword argument at the same time. +- **Example:** + +```ruby +CnpjUtils.new({ formatter: { hidden: true } }, generator: { format: true }) +# raises CnpjUtils::InvalidArgumentCombinationError + +CnpjUtils.format('03603568000195', { hidden: true }, slash_key: '|') +# raises CnpjUtils::InvalidArgumentCombinationError +``` + +- **How to rescue it:** + +```ruby +rescue CnpjUtils::InvalidArgumentCombinationError + # this gem's invalid signature combination + +rescue ArgumentError + # native argument errors, including this gem's InvalidArgumentCombinationError +``` + +##### Rescue granularity + +Each level is shown as its own standalone example (do not merge them into one `rescue` ladder — a broad native handler would make narrower clauses unreachable). + +```ruby +require 'cnpj-utilities' + +# 1) Single native class — catches misuse errors of that kind, +# including non-library ones already handled elsewhere in the consumer's code. +begin + CnpjUtils.new('not-a-hash') +rescue TypeError + # CnpjUtils::TypeMismatchError and any other TypeError (library or not) +end + +begin + CnpjUtils.new({ formatter: { hidden: true } }, generator: { format: true }) +rescue ArgumentError + # CnpjUtils::InvalidArgumentCombinationError and any other ArgumentError (library or not) +end +``` + +```ruby +require 'cnpj-utilities' + +# 2) CnpjUtils::DomainError — not applicable: this gem defines no DomainError +# (and no domain leaves). Domain failures come from bundled packages only. +# begin +# CnpjUtils.new.format(12_345) +# rescue CnpjUtils::DomainError # NameError — constant is not defined +# end +``` + +```ruby +require 'cnpj-utilities' + +# 3) CnpjUtils::Error — catches everything this gem raises, regardless of native ancestry. +# Does not catch CnpjFmt::*, CnpjGen::*, or CnpjVal::* errors. +begin + CnpjUtils.new('not-a-hash') +rescue CnpjUtils::Error + # every custom error that includes CnpjUtils::Error +end +``` + +```ruby +require 'cnpj-utilities' + +# 4) Specific leaf class — catches only that exact failure mode. +begin + CnpjUtils.new('not-a-hash') +rescue CnpjUtils::TypeMismatchError + # only CnpjUtils::TypeMismatchError +end +``` + +#### Propagated from bundled packages + +- **Formatting** (`CnpjFmt`): `CnpjFmt::TypeMismatchError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`, `CnpjFmt::InvalidLengthError` (passed to `on_fail`, not raised by `#format`), and related classes. +- **Generation** (`CnpjGen`): `CnpjGen::TypeMismatchError`, `CnpjGen::ValidationError`, and related classes. +- **Validation** (`CnpjVal`): `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError`, and related classes. + +Invalid option types are typically **`TypeError`** subclasses (`*::TypeMismatchError`); invalid option values are domain errors under each package’s `DomainError` hierarchy. Validation failure returns `false`; formatting length failure is handled by **`on_fail`** (default returns an empty string). + +```ruby +require 'cnpj-utilities' + +begin + CnpjUtils.new.format(12_345) +rescue CnpjFmt::TypeMismatchError => e + puts e.message +end + +begin + CnpjUtils.new.is_valid(12_345_678_000_198) +rescue CnpjVal::TypeMismatchError => e + puts e.message +end + +# Custom on_fail for invalid length +custom_fail = ->(value, _exception) { "Invalid CNPJ: #{value}" } + +CnpjFmt.cnpj_fmt('123', on_fail: custom_fail) # => "Invalid CNPJ: 123" +CnpjFmt.cnpj_fmt('123') # => "" (default on_fail) +``` + +### Bundled packages + +| Package | Main resources | README | +|---------|----------------|--------| +| [`cnpj-fmt`](https://rubygems.org/gems/cnpj-fmt) | `CnpjFmt::CnpjFormatter`, `CnpjFmt::CnpjFormatterOptions`, `CnpjFmt.cnpj_fmt` | [docs](../cnpj-fmt/README.md) | +| [`cnpj-gen`](https://rubygems.org/gems/cnpj-gen) | `CnpjGen::CnpjGenerator`, `CnpjGen::CnpjGeneratorOptions`, `CnpjGen.cnpj_gen` | [docs](../cnpj-gen/README.md) | +| [`cnpj-val`](https://rubygems.org/gems/cnpj-val) | `CnpjVal::CnpjValidator`, `CnpjVal::CnpjValidatorOptions`, `CnpjVal.cnpj_val` | [docs](../cnpj-val/README.md) | + +All of the above are pulled in as dependencies of **`cnpj-utilities`**. For exhaustive option tables, exception lists, and edge-case behavior, see each package README. + +## Contribution & Support + +We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md) for details. If you find this project helpful, please consider: + +- ⭐ Starring the repository +- 🤝 Contributing to the codebase +- 💡 [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues) +- 🐛 [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues) + +## License + +This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details. + +## Changelog + +See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history. + +--- + +Made with ❤️ by [Lacus Solutions](https://github.com/LacusSolutions) diff --git a/packages/cnpj-utilities/README.pt.md b/packages/cnpj-utilities/README.pt.md new file mode 100644 index 0000000..6f62594 --- /dev/null +++ b/packages/cnpj-utilities/README.pt.md @@ -0,0 +1,452 @@ +![cnpj-utilities para Ruby](https://br-utils.vercel.app/img/cover_cnpj-utils.jpg) + +> 🚀 **Suporte total ao [novo formato alfanumérico de CNPJ](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).** + +> 🌎 [Access documentation in English](./README.md) + +Kit em Ruby para formatar, gerar e validar CNPJ (Cadastro Nacional da Pessoa Jurídica). Envolve [`cnpj-fmt`](https://rubygems.org/gems/cnpj-fmt), [`cnpj-gen`](https://rubygems.org/gems/cnpj-gen) e [`cnpj-val`](https://rubygems.org/gems/cnpj-val) em uma única classe fachada (`CnpjUtils`). + +## Recursos + +- ✅ **API unificada**: Helpers de classe `CnpjUtils.format` / `.generate` / `.is_valid` (aliases de `CnpjUtils::DEFAULT`); `DEFAULT` mutável para ajustes compartilhados +- ✅ **Acesso em dois níveis**: Prefira `CnpjUtils::CnpjFormatter` / `CnpjGenerator` / `CnpjValidator` para as classes principais; Options, helpers e erros ficam em `CnpjUtils::CnpjFmt` / `CnpjGen` / `CnpjVal` (os irmãos na raiz `CnpjFmt` / `CnpjGen` / `CnpjVal` continuam funcionando) +- ✅ **CNPJ alfanumérico**: Formatar, gerar e validar CNPJ de 14 caracteres numérico ou alfanumérico +- ✅ **Instância reutilizável**: Classe `CnpjUtils` com configurações padrão opcionais (opções ou instâncias do formatador, gerador e validador) +- ✅ **Entrada flexível**: `#format` e `#is_valid` aceitam `String` ou `Array` de strings (elementos concatenados na ordem) +- ✅ **Sobrescritas por chamada**: Padrões da instância mais um `Hash`/instância `*Options` por chamada **ou** sobrescritas por palavra-chave (não ambos) +- ✅ **Tratamento de erros**: Erros dos componentes propagam inalterados; esta gem define `CnpjUtils::TypeMismatchError` e `CnpjUtils::InvalidArgumentCombinationError` para uso indevido da API + +## Instalação + +Instale a gem diretamente: + +```bash +gem install cnpj-utilities +``` + +Ou adicione ao seu `Gemfile` e execute `bundle install`: + +```ruby +gem 'cnpj-utilities' +``` + +Isso instala **`cnpj-utilities`** junto com [`cnpj-fmt`](https://rubygems.org/gems/cnpj-fmt), [`cnpj-gen`](https://rubygems.org/gems/cnpj-gen) e [`cnpj-val`](https://rubygems.org/gems/cnpj-val). Você **não** precisa de `gem install` / linhas `gem` separados para os pacotes componentes ao usar **`cnpj-utilities`**. + +## Require + +```ruby +require 'cnpj-utilities' +``` + +## Início rápido + +Uso básico com helpers de classe (aliases de `CnpjUtils::DEFAULT`): + +```ruby +require 'cnpj-utilities' + +cnpj = '03603568000195' + +CnpjUtils.format(cnpj) # => "03.603.568/0001-95" +CnpjUtils.format(cnpj, hidden: true) # => "03.603.***/****-**" +CnpjUtils.format( # => "03603568|0001_95" + cnpj, + dot_key: '', + slash_key: '|', + dash_key: '_' +) + +CnpjUtils.generate # => ex.: "AB123CDE000155" (14 caracteres alfanuméricos) +CnpjUtils.generate(format: true) # => ex.: "AB.123.CDE/0001-55" +CnpjUtils.generate(prefix: '45623767') # => ex.: "45623767000296" +CnpjUtils.generate(type: 'numeric') # => ex.: "65453043000178" (apenas dígitos) + +CnpjUtils.is_valid('98765432000198') # => true +CnpjUtils.is_valid('98.765.432/0001-98') # => true +CnpjUtils.is_valid('1QB5UKALPYFP59') # => true (alfanumérico) +CnpjUtils.is_valid('98765432000199') # => false +``` + +## Utilização + +Você pode trabalhar destas formas equivalentes: + +1. **`CnpjUtils.format` / `.generate` / `.is_valid`** — helpers de classe para chamadas rápidas (encaminham para `DEFAULT`). +2. **`CnpjUtils::DEFAULT`** — singleton compartilhado mutável (o mesmo objeto usado pelos helpers de classe). +3. **`CnpjUtils.new`** — instância configurável com padrões compartilhados entre formatar, gerar e validar. +4. **Classes principais sob `CnpjUtils`** — `CnpjUtils::CnpjFormatter`, `CnpjUtils::CnpjGenerator`, `CnpjUtils::CnpjValidator`. +5. **Módulos aninhados do pacote** — Options, helpers, erros e tipos via `CnpjUtils::CnpjFmt` / `CnpjGen` / `CnpjVal` (ex.: `CnpjUtils::CnpjFmt::CnpjFormatterOptions`, `CnpjUtils::CnpjFmt.cnpj_fmt`). +6. **Módulos irmãos na raiz** (ainda suportados) — `CnpjFmt`, `CnpjGen`, `CnpjVal` inalterados. + +Todas as abordagens expõem as mesmas opções e comportamento. Para tabelas de opções exaustivas e detalhes específicos de cada componente, consulte o README de cada [pacote incluído](#pacotes-incluídos). + +### Opções do formatador + +Em `#format(cnpj_input, options = nil, **keywords)`, todas as opções são opcionais: + +| Opção | Tipo | Padrão | Descrição | +|--------|------|---------|-------------| +| `hidden` | `Boolean` | `false` | Se `true`, mascara caracteres entre `hidden_start` e `hidden_end` com `hidden_key` | +| `hidden_key` | `String` | `'*'` | Caractere(s) usados para substituir os caracteres mascarados | +| `hidden_start` | `Integer` | `5` | Índice inicial (0–13, inclusivo) do intervalo a ocultar | +| `hidden_end` | `Integer` | `13` | Índice final (0–13, inclusivo) do intervalo a ocultar | +| `dot_key` | `String` | `'.'` | Delimitador de ponto (ex.: em `12.345.678`) | +| `slash_key` | `String` | `'/'` | Delimitador de barra (ex.: antes da filial `…/0001-90`) | +| `dash_key` | `String` | `'-'` | Delimitador de hífen (ex.: antes dos dígitos verificadores `…-90`) | +| `escape` | `Boolean` | `false` | Se `true`, escapa caracteres especiais HTML no resultado | +| `encode` | `Boolean` | `false` | Se `true`, codifica o resultado para URL (similar ao `encodeURIComponent` do JavaScript) | +| `on_fail` | `Proc` / invocável | retorna `''` | Callback quando o tamanho da entrada sanitizada ≠ 14; o retorno é usado como resultado | + +### Opções do gerador + +Em `#generate(options = nil, **keywords)`, todas as opções são opcionais: + +| Opção | Tipo | Padrão | Descrição | +|--------|------|---------|-------------| +| `format` | `Boolean` | `false` | Se `true`, retorna o CNPJ gerado no formato padrão (`00.000.000/0000-00`) | +| `prefix` | `String` | `''` | String inicial parcial (0–12 caracteres alfanuméricos). Os caracteres faltantes são gerados e os dígitos verificadores calculados. | +| `type` | `String` | `'alphanumeric'` | Conjunto de caracteres da parte gerada aleatoriamente: `'numeric'`, `'alphabetic'` ou `'alphanumeric'`. **Os dígitos verificadores são sempre numéricos.** | + +Regras do prefixo: a base (primeiros 8 caracteres) e a filial (caracteres 9–12) não podem ser todos zeros; 12 dígitos repetidos (ex.: `111111111111`) também não são permitidos. + +### Opções do validador + +Em `#is_valid(cnpj_input, options = nil, **keywords)`, todas as opções são opcionais: + +| Opção | Tipo | Padrão | Descrição | +|--------|------|---------|-------------| +| `case_sensitive` | `Boolean` | `true` | Se `false`, letras minúsculas são aceitas para CNPJ alfanumérico (a entrada é convertida para maiúsculas antes da validação). | +| `type` | `String` | `'alphanumeric'` | `'numeric'`: apenas dígitos (0–9); `'alphanumeric'`: dígitos e letras (0–9, A–Z). | + +### Helpers de classe (`CnpjUtils.format` / `.generate` / `.is_valid`) + +Esses métodos de classe são aliases dos mesmos métodos em `CnpjUtils::DEFAULT`. Prefira-os para chamadas pontuais: + +```ruby +CnpjUtils.format('03603568000195') +CnpjUtils.generate(type: 'numeric') +CnpjUtils.is_valid('98765432000198') +``` + +### `CnpjUtils::DEFAULT` (instância padrão) + +`CnpjUtils::DEFAULT` é o singleton pré-construído e **mutável** por trás dos helpers de classe (paridade com o export padrão do JS / `cnpj_utils` do Python). Mutá-lo afeta chamadas seguintes a `CnpjUtils.format` / `.generate` / `.is_valid`; instâncias `CnpjUtils.new` personalizadas permanecem independentes: + +```ruby +CnpjUtils::DEFAULT.formatter = { slash_key: '|' } +CnpjUtils.format('01ABC234000X56') # => "01.ABC.234|000X-56" + +custom = CnpjUtils.new +custom.format('01ABC234000X56') # => "01.ABC.234/000X-56" (não afetado) +``` + +Métodos de instância em `DEFAULT` (e em qualquer instância de `CnpjUtils`): + +- **`#format(cnpj_input, options = nil, **keywords)`**: Formata uma string CNPJ ou array de strings. Delega ao formatador interno. A entrada deve ter 14 caracteres alfanuméricos (após sanitização); caso contrário, `on_fail` é usado. +- **`#generate(options = nil, **keywords)`**: Gera um CNPJ válido. Delega ao gerador interno. +- **`#is_valid(cnpj_input, options = nil, **keywords)`**: Retorna `true` se o CNPJ for válido. Delega ao validador interno. + +### `CnpjUtils` (classe) + +Para formatador, gerador ou validador padrão personalizados, crie sua própria instância: + +```ruby +require 'cnpj-utilities' + +utils = CnpjUtils.new( + formatter: { hidden: true, hidden_key: '#' }, + generator: { type: 'numeric', format: true }, + validator: { type: 'numeric', case_sensitive: false } +) + +utils.format('RK0CMT3W000100') # => "RK.0CM.###/####-##" +utils.generate # => ex.: "73.008.535/0005-06" +utils.is_valid('98.765.432/0001-98') # => true + +# Acessar ou substituir instâncias internas +utils.formatter # => CnpjFmt::CnpjFormatter +utils.generator # => CnpjGen::CnpjGenerator +utils.validator # => CnpjVal::CnpjValidator +``` + +- **`CnpjUtils.new(settings = nil, **keywords)`**: Configurações opcionais. Passe um `Hash` de settings com as chaves `:formatter`, `:generator` e/ou `:validator`, **ou** as mesmas chaves como argumentos nomeados — não ambos (passar ambos lança `CnpjUtils::InvalidArgumentCombinationError`). Cada valor pode ser uma instância de componente, uma instância `*Options` (armazenada por referência — mutá-la depois afeta chamadas subsequentes sem sobrescrita por chamada), um `Hash` de opções, ou omitido/`nil` para os padrões. +- **`#format(cnpj_input, options = nil, **keywords)`**: Igual à instância padrão; opções por chamada sobrescrevem os padrões do formatador apenas nessa chamada. Passe um `Hash`/`CnpjFmt::CnpjFormatterOptions` **ou** sobrescritas por palavra-chave — não ambos. +- **`#generate(options = nil, **keywords)`**: Igual à instância padrão; opções por chamada sobrescrevem os padrões do gerador. Passe um `Hash`/`CnpjGen::CnpjGeneratorOptions` **ou** sobrescritas por palavra-chave — não ambos. +- **`#is_valid(cnpj_input, options = nil, **keywords)`**: Igual à instância padrão; opções por chamada sobrescrevem os padrões do validador. Passe um `Hash`/`CnpjVal::CnpjValidatorOptions` **ou** sobrescritas por palavra-chave — não ambos. +- **`#formatter`**, **`#generator`**, **`#validator`**: Acessores (getters e setters) dos componentes internos. Os setters aceitam as mesmas formas do construtor. Para alterar uma única opção sem substituir a instância, mute as opções do componente (ex.: `utils.formatter.options.hidden = true`). + +Padrões da instância e sobrescritas por chamada: + +```ruby +require 'cnpj-utilities' + +utils = CnpjUtils.new( + formatter: { hidden: true, hidden_key: '#' }, + generator: { format: true }, + validator: { type: 'numeric' } +) + +cnpj = '03603568000195' + +utils.format(cnpj) # mascarado (padrões do formatador da instância) +utils.format(cnpj, hidden: false) # só nesta chamada: sem máscara +utils.generate(format: false) # só nesta chamada: saída compacta +utils.is_valid('1QB5UKALPYFP59') # => false (validador da instância é só numérico) +utils.is_valid( # => true nesta chamada + '1QB5UKALPYFP59', + type: 'alphanumeric' +) +``` + +As opções também podem ser passadas como `Hash` (ou instância de opções) em cada método — sem sobrescritas por palavra-chave: + +```ruby +utils.format(cnpj, { slash_key: '|' }) +utils.generate({ prefix: '12345', type: 'numeric' }) +utils.is_valid('1QB5UKALPYFP59', { case_sensitive: false }) +``` + +### Usando classes de componente e módulos aninhados + +Caminhos preferidos após `require 'cnpj-utilities'`: + +```ruby +require 'cnpj-utilities' + +# Classes principais na raiz da fachada +formatter = CnpjUtils::CnpjFormatter.new(hidden: true) +generator = CnpjUtils::CnpjGenerator.new(type: 'numeric') +validator = CnpjUtils::CnpjValidator.new + +formatter.format('AB123XYZ000123') # => "AB.123.***/****-**" + +# Options, helpers e erros sob os módulos aninhados do pacote +options = CnpjUtils::CnpjFmt::CnpjFormatterOptions.new(slash_key: '|') +CnpjUtils::CnpjFmt.cnpj_fmt('03603568000195') # => "03.603.568/0001-95" + +begin + CnpjUtils::CnpjFmt.cnpj_fmt(12_345) +rescue CnpjUtils::CnpjFmt::TypeMismatchError + # tipo de entrada incorreto +end +``` + +Os irmãos na raiz continuam suportados (os mesmos objetos que os aninhados): + +```ruby +CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') # => "01.ABC.234|000X-56" +CnpjGen.cnpj_gen(type: 'numeric') # => ex.: "65453043000178" +CnpjVal.cnpj_val('9JN7MGLJZXIO50') # => true +CnpjFmt::CnpjFormatter.new(hidden: true) +``` + +Consulte [`cnpj-fmt`](../cnpj-fmt/README.pt.md), [`cnpj-gen`](../cnpj-gen/README.pt.md) e [`cnpj-val`](../cnpj-val/README.pt.md) para detalhes completos de opções e erros. + +## API + +### Exportações + +Após `require 'cnpj-utilities'`: + +- **`CnpjUtils`**: Classe fachada para criar uma instância com configurações padrão opcionais de formatador, gerador e validador. +- **`CnpjUtils.format` / `.generate` / `.is_valid`**: Helpers de classe que encaminham para `CnpjUtils::DEFAULT`. +- **`CnpjUtils::DEFAULT`**: Instância pré-construída mutável de `CnpjUtils` (o mesmo objeto usado pelos helpers de classe). +- **`CnpjUtils::VERSION`**: String da versão da gem. +- **Atalhos das classes principais**: `CnpjUtils::CnpjFormatter`, `CnpjUtils::CnpjGenerator`, `CnpjUtils::CnpjValidator` (os mesmos objetos das classes irmãs). +- **Módulos aninhados do pacote**: `CnpjUtils::CnpjFmt`, `CnpjUtils::CnpjGen`, `CnpjUtils::CnpjVal` — superfície completa do irmão (Options, helpers, erros, tipos). Options/helpers/erros **não** são aliasados na raiz de `CnpjUtils`. +- **Módulos irmãos na raiz** (ainda suportados): `CnpjFmt`, `CnpjGen`, `CnpjVal` — os mesmos objetos que os aninhados. + +### Erros e exceções + +`CnpjUtils` define apenas erros de uso indevido da API para as regras de argumentos desta gem. Erros de componentes são lançados pelos pacotes incluídos e propagam inalterados. + +#### Definidos por `cnpj-utilities` + +Os erros definidos por esta gem são apenas de **uso indevido da API** (tipo incorreto ou combinação inválida de argumentos). Todo erro customizado inclui o módulo marcador `CnpjUtils::Error`. Esta gem **não** define `CnpjUtils::DomainError` nem folhas de domínio — falhas de domínio vêm apenas dos [pacotes incluídos](#propagados-dos-pacotes-incluídos) e mantêm os namespaces desses pacotes (`CnpjFmt::…`, `CnpjGen::…`, `CnpjVal::…`). + +`rescue CnpjUtils::Error` captura **apenas** erros que esta gem lança. **Não** captura erros de componentes que propagam inalterados. + +##### Resumo + +| Classe | Herda de | Categoria | Condição de disparo | +|--------|----------|-----------|---------------------| +| `CnpjUtils::TypeMismatchError` | `CnpjUtils::TypeMismatchError < TypeError < StandardError` (+ `include CnpjUtils::Error`) | Uso indevido da API | Argumento `settings` não-`nil` de `CnpjUtils.new` não é um `Hash` | +| `CnpjUtils::InvalidArgumentCombinationError` | `CnpjUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include CnpjUtils::Error`) | Uso indevido da API | `Hash`/instância de settings/options não-`nil` passado junto com qualquer argumento nomeado não-`nil` | + +##### `CnpjUtils::Error` (módulo marcador) + +- **Herança:** módulo marcador misturado em todo erro customizado que esta gem lança via `include` (não é uma classe). +- **Categoria:** N/A (apenas alvo de `rescue`) — não é um modo de falha por si só. +- **Quando é lançado:** Nunca é lançado diretamente; incluído em todo erro customizado que esta gem lança. +- **Exemplo:** N/A +- **Como resgatá-lo:** + +```ruby +rescue CnpjUtils::Error + # TypeMismatchError e InvalidArgumentCombinationError apenas desta gem + # (não CnpjFmt::*, CnpjGen::* nem CnpjVal::*) +``` + +##### `CnpjUtils::TypeMismatchError` + +- **Herança:** `CnpjUtils::TypeMismatchError < TypeError < StandardError` (inclui `CnpjUtils::Error`) +- **Categoria:** Uso indevido da API — o chamador passou um valor do tipo errado. +- **Quando é lançado:** Quando `CnpjUtils.new` recebe um argumento `settings` não-`nil` que não é um `Hash`. +- **Exemplo:** + +```ruby +CnpjUtils.new('not-a-hash') # lança CnpjUtils::TypeMismatchError +CnpjUtils.new(false) # lança CnpjUtils::TypeMismatchError (false é não-nil) +``` + +- **Como resgatá-lo:** + +```ruby +rescue CnpjUtils::TypeMismatchError + # violação de contrato de tipo desta gem + +rescue TypeError + # erros nativos de tipo, incluindo TypeMismatchError desta gem +``` + +##### `CnpjUtils::InvalidArgumentCombinationError` + +- **Herança:** `CnpjUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (inclui `CnpjUtils::Error`) +- **Categoria:** Uso indevido da API — o chamador misturou padrões de argumentos mutuamente exclusivos. +- **Quando é lançado:** Quando `CnpjUtils.new`, `#format`, `#generate`, `#is_valid` ou os helpers de classe recebem ao mesmo tempo um `Hash`/instância de settings/options não-`nil` e qualquer argumento nomeado não-`nil`. +- **Exemplo:** + +```ruby +CnpjUtils.new({ formatter: { hidden: true } }, generator: { format: true }) +# lança CnpjUtils::InvalidArgumentCombinationError + +CnpjUtils.format('03603568000195', { hidden: true }, slash_key: '|') +# lança CnpjUtils::InvalidArgumentCombinationError +``` + +- **Como resgatá-lo:** + +```ruby +rescue CnpjUtils::InvalidArgumentCombinationError + # combinação de assinatura inválida desta gem + +rescue ArgumentError + # erros nativos de argumento, incluindo InvalidArgumentCombinationError desta gem +``` + +##### Granularidade de rescue + +Cada nível é mostrado como exemplo isolado (não os una numa única escada de `rescue` — um handler nativo amplo tornaria as cláusulas mais estreitas inalcançáveis). + +```ruby +require 'cnpj-utilities' + +# 1) Uma classe nativa — captura erros de uso indevido daquele tipo, +# inclusive outros TypeError/ArgumentError já tratados no código do consumidor. +begin + CnpjUtils.new('not-a-hash') +rescue TypeError + # CnpjUtils::TypeMismatchError e qualquer outro TypeError (da biblioteca ou não) +end + +begin + CnpjUtils.new({ formatter: { hidden: true } }, generator: { format: true }) +rescue ArgumentError + # CnpjUtils::InvalidArgumentCombinationError e qualquer outro ArgumentError (da biblioteca ou não) +end +``` + +```ruby +require 'cnpj-utilities' + +# 2) CnpjUtils::DomainError — não se aplica: esta gem não define DomainError +# (nem folhas de domínio). Falhas de domínio vêm só dos pacotes incluídos. +# begin +# CnpjUtils.new.format(12_345) +# rescue CnpjUtils::DomainError # NameError — constante não definida +# end +``` + +```ruby +require 'cnpj-utilities' + +# 3) CnpjUtils::Error — captura tudo o que esta gem lança, independentemente da ancestralidade nativa. +# Não captura erros CnpjFmt::*, CnpjGen::* nem CnpjVal::*. +begin + CnpjUtils.new('not-a-hash') +rescue CnpjUtils::Error + # todo erro customizado que inclui CnpjUtils::Error +end +``` + +```ruby +require 'cnpj-utilities' + +# 4) Classe folha específica — captura apenas aquele modo de falha. +begin + CnpjUtils.new('not-a-hash') +rescue CnpjUtils::TypeMismatchError + # apenas CnpjUtils::TypeMismatchError +end +``` + +#### Propagados dos pacotes incluídos + +- **Formatação** (`CnpjFmt`): `CnpjFmt::TypeMismatchError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`, `CnpjFmt::InvalidLengthError` (passado a `on_fail`, não lançado por `#format`) e classes relacionadas. +- **Geração** (`CnpjGen`): `CnpjGen::TypeMismatchError`, `CnpjGen::ValidationError` e classes relacionadas. +- **Validação** (`CnpjVal`): `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError` e classes relacionadas. + +Tipos de opção inválidos são tipicamente subclasses de **`TypeError`** (`*::TypeMismatchError`); valores de opção inválidos são erros de domínio sob a hierarquia `DomainError` de cada pacote. Falha de validação retorna `false`; falha de comprimento na formatação é tratada por **`on_fail`** (o padrão retorna string vazia). + +```ruby +require 'cnpj-utilities' + +begin + CnpjUtils.new.format(12_345) +rescue CnpjFmt::TypeMismatchError => e + puts e.message +end + +begin + CnpjUtils.new.is_valid(12_345_678_000_198) +rescue CnpjVal::TypeMismatchError => e + puts e.message +end + +# on_fail personalizado para comprimento inválido +custom_fail = ->(value, _exception) { "CNPJ inválido: #{value}" } + +CnpjFmt.cnpj_fmt('123', on_fail: custom_fail) # => "CNPJ inválido: 123" +CnpjFmt.cnpj_fmt('123') # => "" (on_fail padrão) +``` + +### Pacotes incluídos + +| Pacote | Principais recursos | README | +|--------|---------------------|--------| +| [`cnpj-fmt`](https://rubygems.org/gems/cnpj-fmt) | `CnpjFmt::CnpjFormatter`, `CnpjFmt::CnpjFormatterOptions`, `CnpjFmt.cnpj_fmt` | [docs](../cnpj-fmt/README.pt.md) | +| [`cnpj-gen`](https://rubygems.org/gems/cnpj-gen) | `CnpjGen::CnpjGenerator`, `CnpjGen::CnpjGeneratorOptions`, `CnpjGen.cnpj_gen` | [docs](../cnpj-gen/README.pt.md) | +| [`cnpj-val`](https://rubygems.org/gems/cnpj-val) | `CnpjVal::CnpjValidator`, `CnpjVal::CnpjValidatorOptions`, `CnpjVal.cnpj_val` | [docs](../cnpj-val/README.pt.md) | + +Todos os pacotes acima são instalados como dependências de **`cnpj-utilities`**. Para tabelas de opções exaustivas, listas de exceções e comportamento em casos extremos, consulte o README de cada pacote. + +## Contribuição e suporte + +Contribuições são bem-vindas! Consulte as [Diretrizes de contribuição](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md). Se o projeto for útil para você, considere: + +- ⭐ Dar uma estrela no repositório +- 🤝 Contribuir com código +- 💡 [Sugerir novas funcionalidades](https://github.com/LacusSolutions/br-utils-ruby/issues) +- 🐛 [Reportar bugs](https://github.com/LacusSolutions/br-utils-ruby/issues) + +## Licença + +Este projeto está sob a licença MIT — veja o arquivo [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE). + +## Changelog + +Veja o [CHANGELOG](./CHANGELOG.md) para alterações e histórico de versões. + +--- + +Feito com ❤️ por [Lacus Solutions](https://github.com/LacusSolutions) diff --git a/packages/cnpj-utilities/cnpj-utilities.gemspec b/packages/cnpj-utilities/cnpj-utilities.gemspec index 367fa75..7f1a4d7 100644 --- a/packages/cnpj-utilities/cnpj-utilities.gemspec +++ b/packages/cnpj-utilities/cnpj-utilities.gemspec @@ -6,14 +6,17 @@ Gem::Specification.new do |spec| spec.name = 'cnpj-utilities' spec.version = CnpjUtils::VERSION spec.authors = ['Julio L. Muller'] - spec.summary = 'CNPJ utilities (Brazilian company ID)' + spec.email = ['juliolmuller@outlook.com'] + spec.summary = 'Utilities to deal with CNPJ (Brazilian Business Tax ID)' + spec.description = 'Utilities to deal with CNPJ (Brazilian Business Tax ID)' spec.homepage = 'https://github.com/LacusSolutions/br-utils-ruby' spec.license = 'MIT' spec.required_ruby_version = '>= 3.1' + spec.metadata['source_code_uri'] = spec.homepage spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] - spec.add_dependency 'cnpj-fmt', '>= 0' - spec.add_dependency 'cnpj-gen', '>= 0' - spec.add_dependency 'cnpj-val', '>= 0' + spec.add_dependency 'cnpj-fmt', '>= 1.0.0', '< 1.1.0' + spec.add_dependency 'cnpj-gen', '>= 1.0.0', '< 1.1.0' + spec.add_dependency 'cnpj-val', '>= 1.0.0', '< 1.1.0' end diff --git a/packages/cnpj-utilities/src/cnpj-utilities.rb b/packages/cnpj-utilities/src/cnpj-utilities.rb index c95dd6c..f084637 100644 --- a/packages/cnpj-utilities/src/cnpj-utilities.rb +++ b/packages/cnpj-utilities/src/cnpj-utilities.rb @@ -5,8 +5,30 @@ require 'cnpj-val' require_relative 'cnpj-utilities/version' -module CnpjUtils - def self.hello - 'cnpj-utils' - end +# Entry point for the +cnpj-utilities+ gem. +# +# Loads sibling packages (+cnpj-fmt+, +cnpj-gen+, +cnpj-val+) and defines the +# {CnpjUtils} façade class. +version.rb+ defines a placeholder module so the +# gemspec can read {CnpjUtils::VERSION}; this file promotes it to the class +# consumers instantiate. +# +# Two-tier access after +require 'cnpj-utilities'+: +# +# - *Main shortcuts* at the façade root: {CnpjUtils::CnpjFormatter}, +# {CnpjUtils::CnpjGenerator}, {CnpjUtils::CnpjValidator}. +# - *Package nests* for the full sibling surface (Options, helpers, errors, +# types): {CnpjUtils::CnpjFmt}, {CnpjUtils::CnpjGen}, {CnpjUtils::CnpjVal} +# (same objects as +::CnpjFmt+, +::CnpjGen+, +::CnpjVal+). +# - Root siblings (+CnpjFmt+, +CnpjGen+, +CnpjVal+) remain supported unchanged. +unless CnpjUtils.is_a?(Class) + version = CnpjUtils::VERSION + Object.send(:remove_const, :CnpjUtils) + CnpjUtils = Class.new + CnpjUtils.const_set(:VERSION, version) end + +require_relative 'cnpj-utilities/errors' +require_relative 'cnpj-utilities/cnpj_utils' +require_relative 'cnpj-utilities/cnpj_fmt' +require_relative 'cnpj-utilities/cnpj_gen' +require_relative 'cnpj-utilities/cnpj_val' diff --git a/packages/cnpj-utilities/src/cnpj-utilities/cnpj_fmt.rb b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_fmt.rb new file mode 100644 index 0000000..49d3c0c --- /dev/null +++ b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_fmt.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class CnpjUtils + # Nested package module — same object as +::CnpjFmt+ (Options, helpers, errors, types). + CnpjFmt = ::CnpjFmt + + CnpjFormatter = CnpjFmt::CnpjFormatter + CnpjFormatterOptions = CnpjFmt::CnpjFormatterOptions + CnpjFormatterError = CnpjFmt::Error +end diff --git a/packages/cnpj-utilities/src/cnpj-utilities/cnpj_gen.rb b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_gen.rb new file mode 100644 index 0000000..adc9760 --- /dev/null +++ b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_gen.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class CnpjUtils + # Nested package module — same object as +::CnpjGen+ (Options, helpers, errors, types). + CnpjGen = ::CnpjGen + + CnpjGenerator = CnpjGen::CnpjGenerator + CnpjGeneratorOptions = CnpjGen::CnpjGeneratorOptions + CnpjGeneratorError = CnpjGen::Error +end diff --git a/packages/cnpj-utilities/src/cnpj-utilities/cnpj_utils.rb b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_utils.rb new file mode 100644 index 0000000..76446cc --- /dev/null +++ b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_utils.rb @@ -0,0 +1,421 @@ +# frozen_string_literal: true + +require 'cnpj-fmt' +require 'cnpj-gen' +require 'cnpj-val' + +require_relative 'errors' + +# Unified API for CNPJ (Cadastro Nacional da Pessoa Jurídica) formatting, +# generation, and validation. Wraps a configurable formatter, generator, and +# validator so you can format, generate, and validate CNPJ values from a single +# instance. +# +# Public API: +# +# - {CnpjUtils.format}, {CnpjUtils.generate}, {CnpjUtils.is_valid} — class helpers +# that alias {CnpjUtils::DEFAULT} (preferred quick path) +# - {CnpjUtils::DEFAULT} — mutable shared singleton (JS/Python parity) +# - {CnpjUtils#format}, {CnpjUtils#generate}, {CnpjUtils#is_valid} — instance API +# - {CnpjUtils::VERSION} +# - {CnpjUtils::InvalidArgumentCombinationError} (API misuse) +# +# Two-tier access: main-class shortcuts ({CnpjUtils::CnpjFormatter}, etc.) and +# nested package modules ({CnpjUtils::CnpjFmt}, etc.). Root siblings {CnpjFmt}, +# {CnpjGen}, and {CnpjVal} remain loadable after +require 'cnpj-utilities'+. +# +# Mutating {CnpjUtils::DEFAULT} (e.g. via setters) affects subsequent class-helper +# calls. Custom {CnpjUtils.new} instances are independent of +DEFAULT+. +# +# @example +# require 'cnpj-utilities' +# +# CnpjUtils.format('03603568000195') # => "03.603.568/0001-95" +# CnpjUtils.generate(type: 'numeric') # => e.g. "65453043000178" +# CnpjUtils.is_valid('91415732000793') # => true +class CnpjUtils + SETTINGS_KEYS = %i[formatter generator validator].freeze + + FORMATTER_OPTION_KEYS = CnpjFmt::CnpjFormatterOptions::OPTION_KEYS + GENERATOR_OPTION_KEYS = CnpjGen::CnpjGeneratorOptions::OPTION_KEYS + VALIDATOR_OPTION_KEYS = CnpjVal::CnpjValidatorOptions::OPTION_KEYS + + private_constant :SETTINGS_KEYS, :FORMATTER_OPTION_KEYS, :GENERATOR_OPTION_KEYS, :VALIDATOR_OPTION_KEYS + + # Internal helpers for constructing owned component instances and merging + # settings / per-call option arguments. + module Helpers + module_function + + def resolve_settings(settings, keywords) + keyword_settings = compact_settings(keywords) + raise_ambiguous_settings! if !settings.nil? && !keyword_settings.empty? + return normalize_settings(settings) unless settings.nil? + + keyword_settings + end + + def normalize_settings(settings) + raise TypeMismatchError, "CnpjUtils settings must be a Hash. Got #{settings.class}." unless settings.is_a?(Hash) + + SETTINGS_KEYS.each_with_object({}) do |key, resolved| + if settings.key?(key) + resolved[key] = settings[key] + elsif settings.key?(key.to_s) + resolved[key] = settings[key.to_s] + end + end + end + + def compact_settings(keywords) + SETTINGS_KEYS.each_with_object({}) do |key, resolved| + value = keywords[key] + resolved[key] = value unless value.nil? + end + end + + def resolve_formatter(value) + return CnpjFmt::CnpjFormatter.new if value.nil? + return value if value.is_a?(CnpjFmt::CnpjFormatter) + return CnpjFmt::CnpjFormatter.new(value) if value.is_a?(CnpjFmt::CnpjFormatterOptions) || value.is_a?(Hash) + + # Duck-typed / test doubles: use the given object by reference (Python parity). + value + end + + def resolve_generator(value) + return CnpjGen::CnpjGenerator.new if value.nil? + return value if value.is_a?(CnpjGen::CnpjGenerator) + return CnpjGen::CnpjGenerator.new(value) if value.is_a?(CnpjGen::CnpjGeneratorOptions) || value.is_a?(Hash) + + # Duck-typed / test doubles: use the given object by reference (Python parity). + value + end + + def resolve_validator(value) + return CnpjVal::CnpjValidator.new if value.nil? + return value if value.is_a?(CnpjVal::CnpjValidator) + return CnpjVal::CnpjValidator.new(value) if value.is_a?(CnpjVal::CnpjValidatorOptions) || value.is_a?(Hash) + + # Duck-typed / test doubles: use the given object by reference (Python parity). + value + end + + def ensure_exclusive_options!(options, keywords, option_keys) + return if options.nil? + return if keywords.none? { |_key, value| !value.nil? } + + raise_ambiguous_options!(option_keys) + end + + def compact_keyword_overrides(keywords, option_keys) + option_keys.each_with_object({}) do |key, overrides| + value = keywords[key] + overrides[key] = value unless value.nil? + end + end + + def raise_ambiguous_settings! + option_keywords = SETTINGS_KEYS.map { |key| "#{key}:" }.join(', ') + + raise InvalidArgumentCombinationError, + 'Pass either a settings Hash to `settings`, or keyword arguments ' \ + "(#{option_keywords}), not both." + end + + def raise_ambiguous_options!(option_keys) + option_keywords = option_keys.map { |key| "#{key}:" }.join(', ') + + raise InvalidArgumentCombinationError, + "Pass either an options instance/Hash to `options`, or keyword arguments (#{option_keywords}), " \ + 'not both.' + end + end + private_constant :Helpers + + # Creates a new {CnpjUtils} with customized options. Each of +:formatter+, + # +:generator+, and +:validator+ can be omitted (defaults are used), or + # provided as an instance, an options object, or a plain {Hash} of options. + # + # When a component instance is passed, it is used directly (same reference). + # When +nil+ is passed for a component, a new instance with default options is + # created. + # + # +settings+ and the keyword arguments are never merged with each other: when + # +settings+ is given (a {Hash} with +:formatter+, +:generator+, and/or + # +:validator+ keys), it alone determines the components; otherwise, the + # components are built exclusively from the keyword arguments. Passing + # +settings+ together with any non-+nil+ keyword argument raises + # {InvalidArgumentCombinationError} instead of silently ignoring the keywords. + # + # @param settings [Hash, nil] settings Hash with +:formatter+, +:generator+, + # and/or +:validator+ keys (each a component instance, options instance, + # options Hash, or +nil+) + # @param keywords [Hash] +:formatter+, +:generator+, +:validator+ (mutually + # exclusive with +settings+) + # @raise [InvalidArgumentCombinationError] if +settings+ and a keyword argument + # are both given + # @raise [TypeMismatchError] if +settings+ is given and is not a +Hash+ + # @raise [CnpjFmt::TypeMismatchError] if formatter options have an invalid type + # @raise [CnpjFmt::OutOfRangeError] if formatter +hidden_start+ or +hidden_end+ + # are out of valid range + # @raise [CnpjFmt::ValidationError] if any formatter key option contains a + # disallowed character + # @raise [CnpjGen::TypeMismatchError] if generator options have an invalid type + # @raise [CnpjGen::ValidationError] if generator +prefix+ is invalid or +type+ + # is not allowed + # @raise [CnpjVal::TypeMismatchError] if validator options have an invalid type + # @raise [CnpjVal::ValidationError] if validator +type+ is not allowed + def initialize(settings = nil, **keywords) + resolved = Helpers.resolve_settings(settings, keywords) + + @formatter = Helpers.resolve_formatter(resolved[:formatter]) + @generator = Helpers.resolve_generator(resolved[:generator]) + @validator = Helpers.resolve_validator(resolved[:validator]) + end + + # Returns the formatter used by this utils instance. + # + # @return [CnpjFmt::CnpjFormatter] + attr_reader :formatter + + # Returns the generator used by this utils instance. + # + # @return [CnpjGen::CnpjGenerator] + attr_reader :generator + + # Returns the validator used by this utils instance. + # + # @return [CnpjVal::CnpjValidator] + attr_reader :validator + + # Sets the active formatter used by this utils instance. + # + # It is flexible and can handle any of these inputs: + # + # 1. A complete new instance of {CnpjFmt::CnpjFormatter} + # 2. An instance of {CnpjFmt::CnpjFormatterOptions} + # 3. A partial {Hash} with options for the formatter + # 4. +nil+ creates a brand new {CnpjFmt::CnpjFormatter} with default options + # + # Note that this resets the formatter instance completely. Any previous + # options will be overridden. To alter only a single option or a few options + # of the existing instance, access it directly (e.g. + # +utils.formatter.options.hidden = true+). + # + # @param value [CnpjFmt::CnpjFormatter, CnpjFmt::CnpjFormatterOptions, Hash, nil] + # @raise [CnpjFmt::TypeMismatchError] if options have an invalid type + # @raise [CnpjFmt::OutOfRangeError] if +hidden_start+ or +hidden_end+ are out + # of valid range + # @raise [CnpjFmt::ValidationError] if any key option contains a disallowed + # character + def formatter=(value) + @formatter = Helpers.resolve_formatter(value) + end + + # Sets the active generator used by this utils instance. + # + # It is flexible and can handle any of these inputs: + # + # 1. A complete new instance of {CnpjGen::CnpjGenerator} + # 2. An instance of {CnpjGen::CnpjGeneratorOptions} + # 3. A partial {Hash} with options for the generator + # 4. +nil+ creates a brand new {CnpjGen::CnpjGenerator} with default options + # + # Note that this resets the generator instance completely. Any previous + # options will be overridden. To alter only a single option or a few options + # of the existing instance, access it directly (e.g. + # +utils.generator.options.type = 'numeric'+). + # + # @param value [CnpjGen::CnpjGenerator, CnpjGen::CnpjGeneratorOptions, Hash, nil] + # @raise [CnpjGen::TypeMismatchError] if options have an invalid type + # @raise [CnpjGen::ValidationError] if +prefix+ is invalid or +type+ is not + # allowed + def generator=(value) + @generator = Helpers.resolve_generator(value) + end + + # Sets the active validator used by this utils instance. + # + # It is flexible and can handle any of these inputs: + # + # 1. A complete new instance of {CnpjVal::CnpjValidator} + # 2. An instance of {CnpjVal::CnpjValidatorOptions} + # 3. A partial {Hash} with options for the validator + # 4. +nil+ creates a brand new {CnpjVal::CnpjValidator} with default options + # + # Note that this resets the validator instance completely. Any previous + # options will be overridden. To alter only a single option or a few options + # of the existing instance, access it directly (e.g. + # +utils.validator.options.type = 'numeric'+). + # + # @param value [CnpjVal::CnpjValidator, CnpjVal::CnpjValidatorOptions, Hash, nil] + # @raise [CnpjVal::TypeMismatchError] if options have an invalid type + # @raise [CnpjVal::ValidationError] if +type+ is not allowed + def validator=(value) + @validator = Helpers.resolve_validator(value) + end + + # Formats a CNPJ value into a human-readable string. + # + # Normalizes and optionally masks, HTML-escapes, or URL-encodes the input. + # Delegates to the instance formatter; per-call options override the + # formatter's defaults for this call only. + # + # Input is normalized by stripping non-alphanumeric characters and converting + # to uppercase. If the result length is not exactly 14, the configured + # +on_fail+ callback is invoked with the original value and an error; its + # return value is used as the result. + # + # When valid, the result may be further transformed according to options: + # + # - If +hidden+ is +true+, characters between +hidden_start+ and +hidden_end+ + # (inclusive) are replaced with +hidden_key+. + # - If +escape+ is +true+, HTML special characters are escaped. + # - If +encode+ is +true+, the string is URL-encoded. + # + # +options+ and the keyword arguments are never merged with each other: when + # +options+ is given alone it is forwarded as the per-call override; otherwise + # any non-+nil+ keyword argument is forwarded. Passing +options+ together with + # any non-+nil+ keyword argument raises {InvalidArgumentCombinationError}. + # + # @param cnpj_input [String, Array] CNPJ value as a string or array of + # strings + # @param options [CnpjFmt::CnpjFormatterOptions, Hash, nil] per-call overrides + # @param keywords [Hash] per-call option keyword overrides (mutually exclusive + # with +options+; see {CnpjFmt::CnpjFormatterOptions}) + # @return [String] formatted CNPJ string, or the +on_fail+ callback result + # @raise [InvalidArgumentCombinationError] if +options+ and a keyword argument + # are both given + # @raise [CnpjFmt::TypeMismatchError] if the input is not a +String+ or + # +Array+, or if any option has an invalid type + # @raise [CnpjFmt::OutOfRangeError] if +hidden_start+ or +hidden_end+ are out + # of valid range + # @raise [CnpjFmt::ValidationError] if any key option contains a disallowed + # character + def format(cnpj_input, options = nil, **keywords) + Helpers.ensure_exclusive_options!(options, keywords, FORMATTER_OPTION_KEYS) + return @formatter.format(cnpj_input, options) unless options.nil? + + keyword_overrides = Helpers.compact_keyword_overrides(keywords, FORMATTER_OPTION_KEYS) + return @formatter.format(cnpj_input, **keyword_overrides) unless keyword_overrides.empty? + + @formatter.format(cnpj_input) + end + + # Generates a valid 14-character CNPJ, optionally with a prefix and + # formatting. + # + # Builds a 14-character CNPJ from the configured +prefix+ (if any), a random + # sequence of the configured character +type+, and two computed check digits. + # If +format+ is enabled, the result is returned as +00.000.000/0000-00+. + # + # Delegates to the instance generator; per-call options override the + # generator's defaults for this call only. + # + # +options+ and the keyword arguments are never merged with each other: when + # +options+ is given alone it is forwarded as the per-call override; otherwise + # any non-+nil+ keyword argument is forwarded. Passing +options+ together with + # any non-+nil+ keyword argument raises {InvalidArgumentCombinationError}. + # + # @param options [CnpjGen::CnpjGeneratorOptions, Hash, nil] per-call overrides + # @param keywords [Hash] per-call option keyword overrides (mutually exclusive + # with +options+; see {CnpjGen::CnpjGeneratorOptions}) + # @return [String] generated CNPJ + # @raise [InvalidArgumentCombinationError] if +options+ and a keyword argument + # are both given + # @raise [CnpjGen::TypeMismatchError] if any option has an invalid type + # @raise [CnpjGen::ValidationError] if +prefix+ is invalid or +type+ is not + # allowed + def generate(options = nil, **keywords) + Helpers.ensure_exclusive_options!(options, keywords, GENERATOR_OPTION_KEYS) + return @generator.generate(options) unless options.nil? + + keyword_overrides = Helpers.compact_keyword_overrides(keywords, GENERATOR_OPTION_KEYS) + return @generator.generate(**keyword_overrides) unless keyword_overrides.empty? + + @generator.generate + end + + # Returns whether the given value is a valid CNPJ. + # + # Delegates to the instance validator; per-call options override the + # validator's defaults for this call only. + # + # +options+ and the keyword arguments are never merged with each other: when + # +options+ is given alone it is forwarded as the per-call override; otherwise + # any non-+nil+ keyword argument is forwarded. Passing +options+ together with + # any non-+nil+ keyword argument raises {InvalidArgumentCombinationError}. + # + # @param cnpj_input [String, Array] CNPJ value as a string or array of + # strings + # @param options [CnpjVal::CnpjValidatorOptions, Hash, nil] per-call overrides + # @param keywords [Hash] per-call option keyword overrides (mutually exclusive + # with +options+; see {CnpjVal::CnpjValidatorOptions}) + # @return [Boolean] +true+ when valid, +false+ otherwise + # @raise [InvalidArgumentCombinationError] if +options+ and a keyword argument + # are both given + # @raise [CnpjVal::TypeMismatchError] if the input is not a +String+ or + # +Array+, or if any option has an invalid type + # @raise [CnpjVal::ValidationError] if the +type+ option is not allowed + # rubocop:disable Naming/PredicatePrefix -- public API matches JS/Python `is_valid` + def is_valid(cnpj_input, options = nil, **keywords) + Helpers.ensure_exclusive_options!(options, keywords, VALIDATOR_OPTION_KEYS) + return @validator.is_valid(cnpj_input, options) unless options.nil? + + keyword_overrides = Helpers.compact_keyword_overrides(keywords, VALIDATOR_OPTION_KEYS) + return @validator.is_valid(cnpj_input, **keyword_overrides) unless keyword_overrides.empty? + + @validator.is_valid(cnpj_input) + end + # rubocop:enable Naming/PredicatePrefix + + # Default {CnpjUtils} instance with default formatter, generator, and + # validator options (parity with the JS default export / Python +cnpj_utils+ + # singleton). Mutating this instance (e.g. via setters) affects subsequent + # {CnpjUtils.format}, {CnpjUtils.generate}, and {CnpjUtils.is_valid} calls. + DEFAULT = new + + class << self + # Formats a CNPJ using {DEFAULT} (alias of {CnpjUtils#format} on that instance). + # + # @param cnpj_input [String, Array] CNPJ value as a string or array of + # strings + # @param options [CnpjFmt::CnpjFormatterOptions, Hash, nil] per-call overrides + # @param keywords [Hash] per-call option keyword overrides (mutually exclusive + # with +options+) + # @return [String] formatted CNPJ string, or the +on_fail+ callback result + # @see CnpjUtils#format + def format(cnpj_input, options = nil, **keywords) + DEFAULT.format(cnpj_input, options, **keywords) + end + + # Generates a valid CNPJ using {DEFAULT} (alias of {CnpjUtils#generate} on that + # instance). + # + # @param options [CnpjGen::CnpjGeneratorOptions, Hash, nil] per-call overrides + # @param keywords [Hash] per-call option keyword overrides (mutually exclusive + # with +options+) + # @return [String] generated CNPJ + # @see CnpjUtils#generate + def generate(options = nil, **keywords) + DEFAULT.generate(options, **keywords) + end + + # Validates a CNPJ using {DEFAULT} (alias of {CnpjUtils#is_valid} on that + # instance). + # + # @param cnpj_input [String, Array] CNPJ value as a string or array of + # strings + # @param options [CnpjVal::CnpjValidatorOptions, Hash, nil] per-call overrides + # @param keywords [Hash] per-call option keyword overrides (mutually exclusive + # with +options+) + # @return [Boolean] +true+ when valid, +false+ otherwise + # @see CnpjUtils#is_valid + # rubocop:disable Naming/PredicatePrefix -- public API matches instance `#is_valid` + def is_valid(cnpj_input, options = nil, **keywords) + DEFAULT.is_valid(cnpj_input, options, **keywords) + end + # rubocop:enable Naming/PredicatePrefix + end +end diff --git a/packages/cnpj-utilities/src/cnpj-utilities/cnpj_val.rb b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_val.rb new file mode 100644 index 0000000..e77178b --- /dev/null +++ b/packages/cnpj-utilities/src/cnpj-utilities/cnpj_val.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class CnpjUtils + # Nested package module — same object as +::CnpjVal+ (Options, helpers, errors, types). + CnpjVal = ::CnpjVal + + CnpjValidator = CnpjVal::CnpjValidator + CnpjValidatorOptions = CnpjVal::CnpjValidatorOptions + CnpjValidatorError = CnpjVal::Error +end diff --git a/packages/cnpj-utilities/src/cnpj-utilities/errors.rb b/packages/cnpj-utilities/src/cnpj-utilities/errors.rb new file mode 100644 index 0000000..04295a4 --- /dev/null +++ b/packages/cnpj-utilities/src/cnpj-utilities/errors.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class CnpjUtils + # Marker module mixed into every custom error raised by this library. + # + # Use +rescue CnpjUtils::Error+ to catch every library error regardless of + # native ancestry. Component packages raise their own error hierarchies; + # this gem only defines the misuse errors it raises itself. + module Error; end + + # API misuse error raised when an argument's runtime type does not match the + # type required by the API contract (for example, a non-Hash +settings+ value). + class TypeMismatchError < TypeError + include Error + end + + # API misuse error raised when the combination of provided arguments does not + # match any valid overload-style signature (for example, a settings/options + # Hash together with keyword overrides). + class InvalidArgumentCombinationError < ArgumentError + include Error + end +end diff --git a/packages/cnpj-utilities/src/cnpj-utilities/version.rb b/packages/cnpj-utilities/src/cnpj-utilities/version.rb index 15ba1dd..daef254 100644 --- a/packages/cnpj-utilities/src/cnpj-utilities/version.rb +++ b/packages/cnpj-utilities/src/cnpj-utilities/version.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true module CnpjUtils + # Gem version string. Placeholder replaced at build/publish time. + # + # @return [String] VERSION = '0.0.0' end diff --git a/packages/cnpj-utilities/tests/cnpj_utilities.spec.rb b/packages/cnpj-utilities/tests/cnpj_utilities.spec.rb deleted file mode 100644 index 80f9a80..0000000 --- a/packages/cnpj-utilities/tests/cnpj_utilities.spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe CnpjUtils do - describe '.hello' do - it 'returns cnpj-utils' do - expect(CnpjUtils.hello).to eq('cnpj-utils') - end - end -end diff --git a/packages/cnpj-utilities/tests/cnpj_utils.spec.rb b/packages/cnpj-utilities/tests/cnpj_utils.spec.rb new file mode 100644 index 0000000..0559269 --- /dev/null +++ b/packages/cnpj-utilities/tests/cnpj_utils.spec.rb @@ -0,0 +1,1097 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# Combined behavioural suite for CnpjUtils (JS / PHP / Python reference tests). +# +# Dropped cases (not meaningful in Ruby): +# - js/packages/cnpj-utils/tests/output.spec.ts — UMD/CJS/ESM bundles, .d.ts wiring, +# global variable attachment, and export-string parsing (JS packaging only). +# - JavaScript prototype spies (spyOn(CnpjFormatter.prototype, ...)) — replaced with +# instance doubles that assert the same façade-forwarding premise. +# - PHP getFormatter() / getGenerator() / getValidator() accessor names — Ruby uses +# #formatter / #generator / #validator (JS/Python parity per AGENTS.md). +# - PHP CnpjType / CnpjValidationType enums — Ruby uses string type values. +# - Python __slots__ / dynamic-attribute restriction — optional in Ruby; AGENTS.md +# does not require freezing or slot-like attribute locking. +# - Python None-kwargs forwarding quirk on format (forwarding nil keys when any +# keyword is present) — match cnpj-fmt Ruby merge semantics instead. +# - Deep sibling exception message / constructor smoke from python package.spec.py — +# those APIs belong to cnpj-fmt / cnpj-gen / cnpj-val; this suite only asserts +# that requiring cnpj-utilities loads those modules and that DEFAULT works. + +def compact_options(**kwargs) + kwargs.compact +end + +def expect_options_containing(actual, expected) + expected.each do |key, value| + expect(actual[key]).to eq(value) + end +end + +FORMAT_FACTORIES = { + constructor_hash: lambda { |cnpj, slash_key = nil| + utils = CnpjUtils.new(formatter: compact_options(slash_key: slash_key)) + utils.format(cnpj) + }, + constructor_options: lambda { |cnpj, slash_key = nil| + options = CnpjFmt::CnpjFormatterOptions.new(compact_options(slash_key: slash_key)) + utils = CnpjUtils.new(formatter: options) + utils.format(cnpj) + }, + method_keywords: lambda { |cnpj, slash_key = nil| + CnpjUtils.new.format(cnpj, slash_key: slash_key) + }, + method_options: lambda { |cnpj, slash_key = nil| + options = CnpjFmt::CnpjFormatterOptions.new(compact_options(slash_key: slash_key)) + CnpjUtils.new.format(cnpj, options) + } +}.freeze + +GENERATE_FACTORIES = { + constructor_hash: lambda { |format: nil, prefix: nil, type: nil| + utils = CnpjUtils.new(generator: compact_options(format: format, prefix: prefix, type: type)) + utils.generate + }, + constructor_options: lambda { |format: nil, prefix: nil, type: nil| + options = CnpjGen::CnpjGeneratorOptions.new( + compact_options(format: format, prefix: prefix, type: type) + ) + utils = CnpjUtils.new(generator: options) + utils.generate + }, + method_keywords: lambda { |format: nil, prefix: nil, type: nil| + CnpjUtils.new.generate(format: format, prefix: prefix, type: type) + }, + method_options: lambda { |format: nil, prefix: nil, type: nil| + options = CnpjGen::CnpjGeneratorOptions.new( + compact_options(format: format, prefix: prefix, type: type) + ) + CnpjUtils.new.generate(options) + } +}.freeze + +IS_VALID_FACTORIES = { + constructor_hash: lambda { |cnpj, type: nil, case_sensitive: nil| + utils = CnpjUtils.new( + validator: compact_options(type: type, case_sensitive: case_sensitive) + ) + utils.is_valid(cnpj) + }, + constructor_options: lambda { |cnpj, type: nil, case_sensitive: nil| + options = CnpjVal::CnpjValidatorOptions.new( + compact_options(type: type, case_sensitive: case_sensitive) + ) + utils = CnpjUtils.new(validator: options) + utils.is_valid(cnpj) + }, + method_keywords: lambda { |cnpj, type: nil, case_sensitive: nil| + CnpjUtils.new.is_valid(cnpj, type: type, case_sensitive: case_sensitive) + }, + method_options: lambda { |cnpj, type: nil, case_sensitive: nil| + options = CnpjVal::CnpjValidatorOptions.new( + compact_options(type: type, case_sensitive: case_sensitive) + ) + CnpjUtils.new.is_valid(cnpj, options) + } +}.freeze + +FORMAT_FACTORY_CONTEXTS = [ + ['when options are passed to the constructor as a Hash', :constructor_hash], + ['when options are passed to the constructor as CnpjFormatterOptions', :constructor_options], + ['when options are passed to #format as keywords', :method_keywords], + ['when options are passed to #format as CnpjFormatterOptions', :method_options] +].freeze + +GENERATE_FACTORY_CONTEXTS = [ + ['when options are passed to the constructor as a Hash', :constructor_hash], + ['when options are passed to the constructor as CnpjGeneratorOptions', :constructor_options], + ['when options are passed to #generate as keywords', :method_keywords], + ['when options are passed to #generate as CnpjGeneratorOptions', :method_options] +].freeze + +IS_VALID_FACTORY_CONTEXTS = [ + ['when options are passed to the constructor as a Hash', :constructor_hash], + ['when options are passed to the constructor as CnpjValidatorOptions', :constructor_options], + ['when options are passed to #is_valid as keywords', :method_keywords], + ['when options are passed to #is_valid as CnpjValidatorOptions', :method_options] +].freeze + +RSpec.describe CnpjUtils do + def default_formatter_options_snapshot + CnpjFmt::CnpjFormatterOptions.new.all + end + + def default_generator_options_snapshot + CnpjGen::CnpjGeneratorOptions.new.all + end + + def default_validator_options_snapshot + CnpjVal::CnpjValidatorOptions.new.all + end + + describe 'DEFAULT' do + it 'is an instance of CnpjUtils' do + expect(described_class::DEFAULT).to be_a(described_class) + end + + it 'exposes format, generate, and is_valid' do + aggregate_failures do + expect(described_class::DEFAULT).to respond_to(:format) + expect(described_class::DEFAULT).to respond_to(:generate) + expect(described_class::DEFAULT).to respond_to(:is_valid) + end + end + end + + describe 'class helpers' do + it 'exposes format, generate, and is_valid' do + aggregate_failures do + expect(described_class).to respond_to(:format) + expect(described_class).to respond_to(:generate) + expect(described_class).to respond_to(:is_valid) + end + end + + context 'when calling through the class' do + it 'formats like DEFAULT' do + expect(described_class.format('03603568000195')).to eq( + described_class::DEFAULT.format('03603568000195') + ) + end + + it 'generates like DEFAULT' do + result = described_class.generate(type: 'numeric', prefix: '123456780001') + expect(result).to match(/\A\d{14}\z/) + expect(described_class.is_valid(result, type: 'numeric')).to be(true) + end + + it 'validates like DEFAULT' do + aggregate_failures do + expect(described_class.is_valid('9JN7MGLJZXIO50')).to eq( + described_class::DEFAULT.is_valid('9JN7MGLJZXIO50') + ) + expect(described_class.is_valid('9JN7MGLJZXIO51')).to eq( + described_class::DEFAULT.is_valid('9JN7MGLJZXIO51') + ) + end + end + end + + context 'when DEFAULT is mutated' do + around do |example| + original_formatter = described_class::DEFAULT.formatter + example.run + described_class::DEFAULT.formatter = original_formatter + end + + it 'affects subsequent class helper calls' do + described_class::DEFAULT.formatter = { slash_key: '|' } + expect(described_class.format('01ABC234000X56')).to eq('01.ABC.234|000X-56') + end + + it 'does not affect a custom instance' do + custom = described_class.new + described_class::DEFAULT.formatter = { slash_key: '|' } + expect(custom.format('01ABC234000X56')).to eq('01.ABC.234/000X-56') + end + end + end + + describe 'loaded sibling packages' do + it 'makes cnpj-fmt symbols available' do + aggregate_failures do + expect(defined?(CnpjFmt::CnpjFormatter)).to eq('constant') + expect(defined?(CnpjFmt::CnpjFormatterOptions)).to eq('constant') + expect(CnpjFmt).to respond_to(:cnpj_fmt) + end + end + + it 'makes cnpj-gen symbols available' do + aggregate_failures do + expect(defined?(CnpjGen::CnpjGenerator)).to eq('constant') + expect(defined?(CnpjGen::CnpjGeneratorOptions)).to eq('constant') + expect(CnpjGen).to respond_to(:cnpj_gen) + end + end + + it 'makes cnpj-val symbols available' do + aggregate_failures do + expect(defined?(CnpjVal::CnpjValidator)).to eq('constant') + expect(defined?(CnpjVal::CnpjValidatorOptions)).to eq('constant') + expect(CnpjVal).to respond_to(:cnpj_val) + end + end + end + + describe 'two-tier CnpjUtils re-exports' do + it 'nests sibling modules as the same objects' do + aggregate_failures do + expect(described_class::CnpjFmt).to equal(CnpjFmt) + expect(described_class::CnpjGen).to equal(CnpjGen) + expect(described_class::CnpjVal).to equal(CnpjVal) + end + end + + it 'aliases main cnpj-fmt classes at the façade root' do + aggregate_failures do + expect(described_class::CnpjFormatter).to equal(CnpjFmt::CnpjFormatter) + expect(described_class::CnpjFormatterOptions).to equal(CnpjFmt::CnpjFormatterOptions) + expect(described_class::CnpjFormatterError).to equal(CnpjFmt::Error) + end + end + + it 'aliases main cnpj-gen classes at the façade root' do + aggregate_failures do + expect(described_class::CnpjGenerator).to equal(CnpjGen::CnpjGenerator) + expect(described_class::CnpjGeneratorOptions).to equal(CnpjGen::CnpjGeneratorOptions) + expect(described_class::CnpjGeneratorError).to equal(CnpjGen::Error) + end + end + + it 'aliases main cnpj-val classes at the façade root' do + aggregate_failures do + expect(described_class::CnpjValidator).to equal(CnpjVal::CnpjValidator) + expect(described_class::CnpjValidatorOptions).to equal(CnpjVal::CnpjValidatorOptions) + expect(described_class::CnpjValidatorError).to equal(CnpjVal::Error) + end + end + end + + describe '#initialize' do + context 'when called with no arguments' do + subject(:utils) { described_class.new } + + it 'creates default component instances' do + aggregate_failures do + expect(utils.formatter).to be_a(CnpjFmt::CnpjFormatter) + expect(utils.generator).to be_a(CnpjGen::CnpjGenerator) + expect(utils.validator).to be_a(CnpjVal::CnpjValidator) + end + end + + it 'uses default component options' do + aggregate_failures do + expect_options_containing(utils.formatter.options.all, default_formatter_options_snapshot) + expect_options_containing(utils.generator.options.all, default_generator_options_snapshot) + expect_options_containing(utils.validator.options.all, default_validator_options_snapshot) + end + end + end + + context 'when called with component instances' do + it 'uses the passed formatter directly' do + formatter = CnpjFmt::CnpjFormatter.new + utils = described_class.new(formatter: formatter) + + aggregate_failures do + expect(utils.formatter).to be_a(CnpjFmt::CnpjFormatter) + expect(utils.formatter).to equal(formatter) + end + end + + it 'uses the passed generator directly' do + generator = CnpjGen::CnpjGenerator.new + utils = described_class.new(generator: generator) + + aggregate_failures do + expect(utils.generator).to be_a(CnpjGen::CnpjGenerator) + expect(utils.generator).to equal(generator) + end + end + + it 'uses the passed validator directly' do + validator = CnpjVal::CnpjValidator.new + utils = described_class.new(validator: validator) + + aggregate_failures do + expect(utils.validator).to be_a(CnpjVal::CnpjValidator) + expect(utils.validator).to equal(validator) + end + end + + it 'uses all passed components directly' do + formatter = CnpjFmt::CnpjFormatter.new + generator = CnpjGen::CnpjGenerator.new + validator = CnpjVal::CnpjValidator.new + utils = described_class.new( + formatter: formatter, + generator: generator, + validator: validator + ) + + aggregate_failures do + expect(utils.formatter).to equal(formatter) + expect(utils.generator).to equal(generator) + expect(utils.validator).to equal(validator) + end + end + end + + context 'when called with options instances' do + it 'builds a formatter that keeps the options reference' do + formatter_options = CnpjFmt::CnpjFormatterOptions.new + utils = described_class.new(formatter: formatter_options) + + aggregate_failures do + expect(utils.formatter).to be_a(CnpjFmt::CnpjFormatter) + expect(utils.formatter.options).to equal(formatter_options) + end + end + + it 'builds a generator that keeps the options reference' do + generator_options = CnpjGen::CnpjGeneratorOptions.new + utils = described_class.new(generator: generator_options) + + aggregate_failures do + expect(utils.generator).to be_a(CnpjGen::CnpjGenerator) + expect(utils.generator.options).to equal(generator_options) + end + end + + it 'builds a validator that keeps the options reference' do + validator_options = CnpjVal::CnpjValidatorOptions.new + utils = described_class.new(validator: validator_options) + + aggregate_failures do + expect(utils.validator).to be_a(CnpjVal::CnpjValidator) + expect(utils.validator.options).to equal(validator_options) + end + end + + it 'builds all components from the passed options' do + formatter_options = CnpjFmt::CnpjFormatterOptions.new + generator_options = CnpjGen::CnpjGeneratorOptions.new + validator_options = CnpjVal::CnpjValidatorOptions.new + utils = described_class.new( + formatter: formatter_options, + generator: generator_options, + validator: validator_options + ) + + aggregate_failures do + expect(utils.formatter.options).to equal(formatter_options) + expect(utils.generator.options).to equal(generator_options) + expect(utils.validator.options).to equal(validator_options) + end + end + + it 'reflects later mutations on shared options' do + generator_options = CnpjGen::CnpjGeneratorOptions.new(format: false, type: 'numeric') + utils = described_class.new(generator: generator_options) + + generator_options.format = true + generator_options.type = 'alphabetic' + + aggregate_failures do + expect(utils.generator.options.all[:format]).to be(true) + expect(utils.generator.options.all[:type]).to eq('alphabetic') + end + end + end + + context 'when called with partial option hashes' do + let(:formatter_options) do + { + hidden: true, + hidden_key: '#', + hidden_start: 8, + hidden_end: 11, + dot_key: '_', + slash_key: '|', + dash_key: ' dv ' + } + end + + let(:generator_options) do + { + format: true, + prefix: '12345678', + type: 'numeric' + } + end + + let(:validator_options) do + { + case_sensitive: true, + type: 'numeric' + } + end + + it 'creates a formatter with the passed options' do + utils = described_class.new(formatter: formatter_options) + + aggregate_failures do + expect(utils.formatter).to be_a(CnpjFmt::CnpjFormatter) + expect_options_containing(utils.formatter.options.all, formatter_options) + end + end + + it 'creates a generator with the passed options' do + utils = described_class.new(generator: generator_options) + + aggregate_failures do + expect(utils.generator).to be_a(CnpjGen::CnpjGenerator) + expect_options_containing(utils.generator.options.all, generator_options) + end + end + + it 'creates a validator with the passed options' do + utils = described_class.new(validator: validator_options) + + aggregate_failures do + expect(utils.validator).to be_a(CnpjVal::CnpjValidator) + expect_options_containing(utils.validator.options.all, validator_options) + end + end + + it 'creates all components with the passed options' do + utils = described_class.new( + formatter: formatter_options, + generator: generator_options, + validator: validator_options + ) + + aggregate_failures do + expect_options_containing(utils.formatter.options.all, formatter_options) + expect_options_containing(utils.generator.options.all, generator_options) + expect_options_containing(utils.validator.options.all, validator_options) + end + end + + it 'configures components from mixed hashes' do + formatter_hash = { slash_key: '|' } + generator_hash = { format: true, prefix: '12345' } + validator_hash = { type: 'numeric', case_sensitive: false } + + utils = described_class.new( + formatter: formatter_hash, + generator: generator_hash, + validator: validator_hash + ) + + aggregate_failures do + expect_options_containing(utils.formatter.options.all, formatter_hash) + expect_options_containing(utils.generator.options.all, generator_hash) + expect_options_containing(utils.validator.options.all, validator_hash) + end + end + end + + context 'when called with a settings Hash' do + it 'accepts formatter, generator, and validator keys' do + formatter = CnpjFmt::CnpjFormatter.new + generator = CnpjGen::CnpjGenerator.new + validator = CnpjVal::CnpjValidator.new + + utils = described_class.new( + { + formatter: formatter, + generator: generator, + validator: validator + } + ) + + aggregate_failures do + expect(utils.formatter).to equal(formatter) + expect(utils.generator).to equal(generator) + expect(utils.validator).to equal(validator) + end + end + end + + context 'when called with a non-Hash settings value' do + it 'raises TypeMismatchError' do + expect { described_class.new('not-a-hash') } + .to raise_error(CnpjUtils::TypeMismatchError, /settings must be a Hash/) + end + + it 'raises TypeMismatchError for false (non-nil falsy settings)' do + expect { described_class.new(false) } + .to raise_error(CnpjUtils::TypeMismatchError, /settings must be a Hash/) + end + + it 'is rescuable via CnpjUtils::Error' do + expect { described_class.new([]) } + .to raise_error(CnpjUtils::Error) + end + end + + context 'when called with invalid formatter options' do + it 'raises OutOfRangeError for a bad hidden_start' do + expect { described_class.new(formatter: { hidden_start: -1 }) } + .to raise_error(CnpjFmt::OutOfRangeError) + end + + it 'raises ValidationError for a forbidden key character' do + expect { described_class.new(formatter: { dash_key: "\u00e5" }) } + .to raise_error(CnpjFmt::ValidationError) + end + end + + context 'when called with invalid generator options' do + it 'raises ValidationError for an invalid prefix' do + expect { described_class.new(generator: { prefix: '00000000' }) } + .to raise_error(CnpjGen::ValidationError) + end + + it 'raises ValidationError for an invalid type' do + expect { described_class.new(generator: { type: 'invalid' }) } + .to raise_error(CnpjGen::ValidationError) + end + + it 'raises TypeMismatchError for a non-string prefix' do + expect { described_class.new(generator: { prefix: 123 }) } + .to raise_error(CnpjGen::TypeMismatchError) + end + end + + context 'when called with invalid validator options' do + it 'raises ValidationError for an invalid type' do + expect { described_class.new(validator: { type: 'invalid' }) } + .to raise_error(CnpjVal::ValidationError) + end + end + + context 'when called with both a settings Hash and keywords' do + it 'raises InvalidArgumentCombinationError' do + expect do + described_class.new({ formatter: {} }, generator: CnpjGen::CnpjGenerator.new) + end.to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + + it 'raises InvalidArgumentCombinationError for false settings with keywords' do + expect do + described_class.new(false, formatter: {}) + end.to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + end + end + + describe 'resource accessors' do + subject(:utils) { described_class.new } + + it 'returns the formatter used internally' do + expect(utils.formatter).to be_a(CnpjFmt::CnpjFormatter) + end + + it 'returns the generator used internally' do + expect(utils.generator).to be_a(CnpjGen::CnpjGenerator) + end + + it 'returns the validator used internally' do + expect(utils.validator).to be_a(CnpjVal::CnpjValidator) + end + end + + describe '#formatter=' do + subject(:utils) { described_class.new } + + context 'when called with a CnpjFormatter instance' do + it 'sets the formatter instance' do + formatter = CnpjFmt::CnpjFormatter.new + + utils.formatter = formatter + + expect(utils.formatter).to equal(formatter) + end + end + + context 'when called with a CnpjFormatterOptions instance' do + it 'sets a formatter that keeps the options' do + formatter_options = CnpjFmt::CnpjFormatterOptions.new + + utils.formatter = formatter_options + + expect(utils.formatter.options).to equal(formatter_options) + end + end + + context 'when called with a partial options Hash' do + let(:formatter_options) do + { + hidden: true, + hidden_key: '#', + hidden_start: 8, + hidden_end: 11, + dot_key: '_', + slash_key: '|', + dash_key: ' dv ' + } + end + + it 'sets a formatter with the given options' do + utils.formatter = formatter_options + + expect_options_containing(utils.formatter.options.all, formatter_options) + end + + it 'replaces the formatter when given an empty Hash' do + original_formatter = utils.formatter + original_options = original_formatter.options.all + + utils.formatter = {} + + aggregate_failures do + expect(utils.formatter).not_to equal(original_formatter) + expect_options_containing(utils.formatter.options.all, original_options) + end + end + end + end + + describe '#generator=' do + subject(:utils) { described_class.new } + + context 'when called with a CnpjGenerator instance' do + it 'sets the generator instance' do + generator = CnpjGen::CnpjGenerator.new + + utils.generator = generator + + expect(utils.generator).to equal(generator) + end + end + + context 'when called with a CnpjGeneratorOptions instance' do + it 'sets a generator that keeps the options' do + generator_options = CnpjGen::CnpjGeneratorOptions.new + + utils.generator = generator_options + + expect(utils.generator.options).to equal(generator_options) + end + end + + context 'when called with a partial options Hash' do + let(:generator_options) do + { + format: true, + prefix: '12345678', + type: 'numeric' + } + end + + it 'sets a generator with the given options' do + utils.generator = generator_options + + expect_options_containing(utils.generator.options.all, generator_options) + end + + it 'replaces the generator when given an empty Hash' do + original_generator = utils.generator + original_options = original_generator.options.all + + utils.generator = {} + + aggregate_failures do + expect(utils.generator).not_to equal(original_generator) + expect_options_containing(utils.generator.options.all, original_options) + end + end + end + end + + describe '#validator=' do + subject(:utils) { described_class.new } + + context 'when called with a CnpjValidator instance' do + it 'sets the validator instance' do + validator = CnpjVal::CnpjValidator.new + + utils.validator = validator + + expect(utils.validator).to equal(validator) + end + end + + context 'when called with a CnpjValidatorOptions instance' do + it 'sets a validator that keeps the options' do + validator_options = CnpjVal::CnpjValidatorOptions.new + + utils.validator = validator_options + + expect(utils.validator.options).to equal(validator_options) + end + end + + context 'when called with a partial options Hash' do + let(:validator_options) do + { + case_sensitive: true, + type: 'numeric' + } + end + + it 'sets a validator with the given options' do + utils.validator = validator_options + + expect_options_containing(utils.validator.options.all, validator_options) + end + + it 'replaces the validator when given an empty Hash' do + original_validator = utils.validator + original_options = original_validator.options.all + + utils.validator = {} + + aggregate_failures do + expect(utils.validator).not_to equal(original_validator) + expect_options_containing(utils.validator.options.all, original_options) + end + end + end + end + + describe '#format' do + subject(:utils) { described_class.new } + + context 'when delegating to the owned formatter' do + let(:formatter) { instance_double(CnpjFmt::CnpjFormatter) } + + before do + utils.formatter = formatter + end + + it 'invokes format with the same arguments' do + cnpj = 'AB123CDE000145' + options = CnpjFmt::CnpjFormatterOptions.new + allow(formatter).to receive(:format).and_return('formatted') + + utils.format(cnpj, options) + + expect(formatter).to have_received(:format).with(cnpj, options) + end + + it 'returns the formatted CNPJ' do + allow(formatter).to receive(:format).and_return('formatted-cnpj') + + expect(utils.format('12345678000190')).to eq('formatted-cnpj') + end + + it 'forwards named formatting keywords' do + allow(formatter).to receive(:format).and_return('12.345.678/0001-90') + + result = utils.format('12345678000190', hidden: true, hidden_key: 'X', escape: true) + + aggregate_failures do + expect(result).to eq('12.345.678/0001-90') + expect(formatter).to have_received(:format).with( + '12345678000190', + hidden: true, + hidden_key: 'X', + escape: true + ) + end + end + + it 'rethrows errors from the formatter' do + allow(formatter).to receive(:format).and_raise(RuntimeError, 'test error') + + expect { utils.format('12345678000190') }.to raise_error(RuntimeError, 'test error') + end + end + + context 'when constructor formatter defaults are set' do + it 'applies them when method options are omitted' do + utils = described_class.new( + formatter: { + hidden: true, + hidden_key: '#' + } + ) + + expect(utils.format('12ABC34500DE99')).to include('#') + end + end + + context 'when options and keywords are both given' do + it 'raises InvalidArgumentCombinationError for an options instance' do + options = CnpjFmt::CnpjFormatterOptions.new(slash_key: '|') + + expect { utils.format('91415732000793', options, hidden: true) } + .to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + + it 'raises InvalidArgumentCombinationError for an options Hash' do + expect { utils.format('91415732000793', { slash_key: '|' }, hidden: true) } + .to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + end + + FORMAT_FACTORY_CONTEXTS.each do |context_description, factory_key| + context context_description do + let(:format_cnpj) { FORMAT_FACTORIES.fetch(factory_key) } + + it 'matches CnpjFormatter#format behaviour' do + input = '91415732000793' + formatter = CnpjFmt::CnpjFormatter.new + + expect(format_cnpj.call(input)).to eq(formatter.format(input)) + end + + it 'forwards formatting options' do + input = '01ABC234000X56' + slash_key = '|' + + expect(format_cnpj.call(input, slash_key)).to eq("01.ABC.234#{slash_key}000X-56") + end + end + end + end + + describe '#generate' do + subject(:utils) { described_class.new } + + context 'when delegating to the owned generator' do + let(:generator) { instance_double(CnpjGen::CnpjGenerator) } + + before do + utils.generator = generator + end + + it 'invokes generate with the same arguments' do + options = CnpjGen::CnpjGeneratorOptions.new + allow(generator).to receive(:generate).and_return('generated') + + utils.generate(options) + + expect(generator).to have_received(:generate).with(options) + end + + it 'returns the generated CNPJ' do + allow(generator).to receive(:generate).and_return('generated-cnpj') + + expect(utils.generate).to eq('generated-cnpj') + end + + it 'forwards named generation keywords' do + allow(generator).to receive(:generate).and_return('12.345.678/0001-90') + + result = utils.generate(format: true, prefix: '12345678') + + aggregate_failures do + expect(result).to eq('12.345.678/0001-90') + expect(generator).to have_received(:generate).with(format: true, prefix: '12345678') + end + end + + it 'rethrows errors from the generator' do + allow(generator).to receive(:generate).and_raise(RuntimeError, 'test error') + + expect { utils.generate }.to raise_error(RuntimeError, 'test error') + end + end + + context 'when options and keywords are both given' do + it 'raises InvalidArgumentCombinationError for an options instance' do + options = CnpjGen::CnpjGeneratorOptions.new(format: true) + + expect { utils.generate(options, prefix: '12345') } + .to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + + it 'raises InvalidArgumentCombinationError for an options Hash' do + expect { utils.generate({ format: true }, prefix: '12345') } + .to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + end + + GENERATE_FACTORY_CONTEXTS.each do |context_description, factory_key| + context context_description do + let(:generate) { GENERATE_FACTORIES.fetch(factory_key) } + + it 'matches CnpjGenerator#generate shape' do + generator = CnpjGen::CnpjGenerator.new + result = generate.call + + aggregate_failures do + expect(result).to match(/\A[0-9A-Z]{14}\z/) + expect(result.length).to eq(generator.generate.length) + end + end + + it 'forwards generation options' do + result = generate.call(format: true, prefix: '12345', type: 'numeric') + + expect(result).to match(%r{\A12\.345\.\d{3}/\d{4}-\d{2}\z}) + end + + it 'returns a deterministic CNPJ for a full prefix' do + prefix = '123456780009' + results = Array.new(20) { generate.call(prefix: prefix) } + + expect(results.uniq.size).to eq(1) + end + end + end + end + + describe '#is_valid' do + subject(:utils) { described_class.new } + + context 'when delegating to the owned validator' do + let(:validator) { instance_double(CnpjVal::CnpjValidator) } + + before do + utils.validator = validator + end + + it 'invokes is_valid with the same arguments' do + cnpj = 'AB123CDE000145' + options = CnpjVal::CnpjValidatorOptions.new + allow(validator).to receive(:is_valid).and_return(true) + + utils.is_valid(cnpj, options) + + expect(validator).to have_received(:is_valid).with(cnpj, options) + end + + it 'returns the validation result' do + allow(validator).to receive(:is_valid).and_return(true) + + expect(utils.is_valid('AB123CDE000145')).to be(true) + end + + it 'returns false when the validator returns false' do + allow(validator).to receive(:is_valid).and_return(false) + + result = utils.is_valid('12345678000199') + + aggregate_failures do + expect(result).to be(false) + expect(validator).to have_received(:is_valid).with('12345678000199') + end + end + + it 'rethrows errors from the validator' do + allow(validator).to receive(:is_valid).and_raise(RuntimeError, 'test error') + + expect { utils.is_valid('AB123CDE000145') }.to raise_error(RuntimeError, 'test error') + end + end + + context 'when options and keywords are both given' do + it 'raises InvalidArgumentCombinationError for an options instance' do + options = CnpjVal::CnpjValidatorOptions.new(type: 'numeric') + + expect { utils.is_valid('1QB5UKALPYFP59', options, case_sensitive: false) } + .to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + + it 'raises InvalidArgumentCombinationError for an options Hash' do + expect { utils.is_valid('1QB5UKALPYFP59', { type: 'numeric' }, case_sensitive: false) } + .to raise_error(CnpjUtils::InvalidArgumentCombinationError) + end + end + + IS_VALID_FACTORY_CONTEXTS.each do |context_description, factory_key| + context context_description do + let(:is_valid) { IS_VALID_FACTORIES.fetch(factory_key) } + + it 'matches CnpjValidator#is_valid behaviour' do + input = '91415732000793' + validator = CnpjVal::CnpjValidator.new + + expect(is_valid.call(input)).to eq(validator.is_valid(input)) + end + + it 'forwards validation options' do + input = '1QB5UKALPYFP59' + + aggregate_failures do + expect(is_valid.call(input, type: 'numeric')).to be(false) + expect(is_valid.call(input, type: 'alphanumeric')).to be(true) + end + end + + it 'validates formatted and unformatted CNPJs' do + aggregate_failures do + expect(is_valid.call('1QB5UKALPYFP59')).to be(true) + expect(is_valid.call('1QB5.UKAL.PYF/P59')).to be(true) + expect(is_valid.call('AB123CDE0001555')).to be(false) + end + end + end + end + end + + describe 'integration' do + it 'uses the owned component instances for all methods' do + utils = described_class.new + formatter = instance_double(CnpjFmt::CnpjFormatter) + generator = instance_double(CnpjGen::CnpjGenerator) + validator = instance_double(CnpjVal::CnpjValidator) + + allow(formatter).to receive(:format).and_return('formatted') + allow(generator).to receive(:generate).and_return('generated') + allow(validator).to receive(:is_valid).and_return(true) + + utils.formatter = formatter + utils.generator = generator + utils.validator = validator + + aggregate_failures do + expect(utils.format('123')).to eq('formatted') + expect(utils.generate).to eq('generated') + expect(utils.is_valid('123')).to be(true) + expect(formatter).to have_received(:format).once + expect(generator).to have_received(:generate).once + expect(validator).to have_received(:is_valid).once + end + end + end + + describe 'package smoke' do + it 'formats through DEFAULT with a custom slash_key' do + result = described_class::DEFAULT.format('01ABC234000X56', slash_key: '|') + + expect(result).to eq('01.ABC.234|000X-56') + end + + it 'formats through CnpjFmt.cnpj_fmt' do + result = CnpjFmt.cnpj_fmt('01ABC234000X56', slash_key: '|') + + expect(result).to eq('01.ABC.234|000X-56') + end + + it 'formats through an owned CnpjFormatter' do + formatter = CnpjFmt::CnpjFormatter.new(hidden: true) + + expect(formatter.format('AB123XYZ000123')).to eq('AB.123.***/****-**') + end + + it 'generates a numeric CNPJ through DEFAULT' do + result = described_class::DEFAULT.generate(type: 'numeric') + + aggregate_failures do + expect(result.length).to eq(14) + expect(result).to match(/\A\d{14}\z/) + end + end + + it 'generates through CnpjGen.cnpj_gen' do + result = CnpjGen.cnpj_gen(type: 'numeric') + + aggregate_failures do + expect(result.length).to eq(14) + expect(result).to match(/\A\d{14}\z/) + end + end + + it 'validates through DEFAULT' do + aggregate_failures do + expect(described_class::DEFAULT.is_valid('9JN7MGLJZXIO50')).to be(true) + expect(described_class::DEFAULT.is_valid('9JN7MGLJZXIO51')).to be(false) + end + end + + it 'validates through CnpjVal.cnpj_val' do + aggregate_failures do + expect(CnpjVal.cnpj_val('9JN7MGLJZXIO50')).to be(true) + expect(CnpjVal.cnpj_val('9JN7MGLJZXIO51')).to be(false) + end + end + end +end diff --git a/packages/cnpj-val/README.md b/packages/cnpj-val/README.md index 3a96915..87eaa28 100644 --- a/packages/cnpj-val/README.md +++ b/packages/cnpj-val/README.md @@ -61,11 +61,11 @@ validator.is_valid('98765432000198') # => true validator.is_valid('98.765.432/0001-98') # => true validator.is_valid('98765432000199') # => false -validator.is_valid('1QB5UKALPYFP59') # => true (alphanumeric) -validator.is_valid('1QB5UKALpyfp59') # => false (default is case-sensitive) -validator.is_valid('1QB5UKALpyfp59', case_sensitive: false) # => true +validator.is_valid('1QB5UKALPYFP59') # => true (alphanumeric) +validator.is_valid('1QB5UKALpyfp59') # => false (default is case-sensitive) +validator.is_valid('1QB5UKALpyfp59', case_sensitive: false) # => true -validator.is_valid('96206256120884') # => true (numeric) +validator.is_valid('96206256120884') # => true (numeric) validator.is_valid('1QB5UKALPYFP59', type: 'numeric') # => false (letters stripped → length ≠ 14) ``` @@ -74,9 +74,9 @@ Functional helper: ```ruby require 'cnpj-val' -CnpjVal.cnpj_val('98765432000198') # => true -CnpjVal.cnpj_val('98.765.432/0001-98') # => true -CnpjVal.cnpj_val('98765432000199') # => false +CnpjVal.cnpj_val('98765432000198') # => true +CnpjVal.cnpj_val('98.765.432/0001-98') # => true +CnpjVal.cnpj_val('98765432000199') # => false ``` ## Usage @@ -102,7 +102,11 @@ validator = CnpjVal::CnpjValidator.new(type: 'numeric') validator.is_valid('98.765.432/0001-98') # => true validator.is_valid('1QB5UKALPYFP59') # => false (letters stripped → length ≠ 14) -validator.is_valid('1QB5UKALpyfp59', type: 'alphanumeric', case_sensitive: false) # => true +validator.is_valid( # => true + '1QB5UKALpyfp59', + type: 'alphanumeric', + case_sensitive: false +) ``` Default options on the instance; per-call overrides: @@ -112,9 +116,9 @@ require 'cnpj-val' validator = CnpjVal::CnpjValidator.new(case_sensitive: false) -validator.is_valid('1qb5ukalpyfp59') # => true (instance defaults) -validator.is_valid('1qb5ukalpyfp59', case_sensitive: true) # this call only: false -validator.is_valid('1qb5ukalpyfp59') # => true again +validator.is_valid('1qb5ukalpyfp59') # => true (instance defaults) +validator.is_valid('1qb5ukalpyfp59', case_sensitive: true) # this call only: false +validator.is_valid('1qb5ukalpyfp59') # => true again ``` ### `CnpjVal::CnpjValidatorOptions` @@ -128,10 +132,10 @@ Holds validator settings (`case_sensitive`, `type`). Construct with an optional require 'cnpj-val' options = CnpjVal::CnpjValidatorOptions.new(case_sensitive: false, type: 'numeric') -options.case_sensitive # => false -options.type # => "numeric" -options.set({ type: 'alphanumeric' }) # merge and return self -options.all # => frozen snapshot of current options +options.case_sensitive # => false +options.type # => "numeric" +options.set({ type: 'alphanumeric' }) # merge and return self +options.all # => frozen snapshot of current options ``` ### Functional helper @@ -141,13 +145,13 @@ options.all # => frozen snapshot of current options ```ruby require 'cnpj-val' -CnpjVal.cnpj_val('98765432000198') # => true -CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true -CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false -CnpjVal.cnpj_val('1QB5UKALpyfp59', { # Hash form +CnpjVal.cnpj_val('98765432000198') # => true +CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true +CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false +CnpjVal.cnpj_val('1QB5UKALpyfp59', { # Hash form type: 'alphanumeric', case_sensitive: false, -}) # => true +}) # => true ``` ### Input formats @@ -159,8 +163,8 @@ CnpjVal.cnpj_val('1QB5UKALpyfp59', { # Hash form ```ruby require 'cnpj-val' -CnpjVal.cnpj_val(['1', 'Q', 'B', '5', 'U', 'K', 'A', 'L', 'P', 'Y', 'F', 'P', '5', '9']) # => true -CnpjVal.cnpj_val(['1Q.B5U', 'KAL', 'PYFP-59']) # => true +CnpjVal.cnpj_val(['1', 'Q', 'B', '5', 'U', 'K', 'A', 'L', 'P', 'Y', 'F', 'P', '5', '9']) # => true +CnpjVal.cnpj_val(['1Q.B5U', 'KAL', 'PYFP-59']) # => true ``` ### Validation options @@ -239,8 +243,8 @@ rescue CnpjVal::DomainError - **Example:** ```ruby -CnpjVal.cnpj_val(12_345_678_000_198) # raises CnpjVal::TypeMismatchError -CnpjVal.cnpj_val('98765432000198', type: 123) # raises CnpjVal::TypeMismatchError +CnpjVal.cnpj_val(12_345_678_000_198) # raises CnpjVal::TypeMismatchError +CnpjVal.cnpj_val('98765432000198', type: 123) # raises CnpjVal::TypeMismatchError ``` - **How to rescue it:** @@ -287,7 +291,7 @@ rescue ArgumentError - **Example:** ```ruby -CnpjVal.cnpj_val('98765432000198', type: 'invalid') # raises CnpjVal::ValidationError +CnpjVal.cnpj_val('98765432000198', type: 'invalid') # raises CnpjVal::ValidationError ``` - **How to rescue it:** diff --git a/packages/cnpj-val/README.pt.md b/packages/cnpj-val/README.pt.md index 4813dfb..7911a51 100644 --- a/packages/cnpj-val/README.pt.md +++ b/packages/cnpj-val/README.pt.md @@ -48,11 +48,11 @@ validator.is_valid('98765432000198') # => true validator.is_valid('98.765.432/0001-98') # => true validator.is_valid('98765432000199') # => false -validator.is_valid('1QB5UKALPYFP59') # => true (alfanumérico) -validator.is_valid('1QB5UKALpyfp59') # => false (padrão é case-sensitive) -validator.is_valid('1QB5UKALpyfp59', case_sensitive: false) # => true +validator.is_valid('1QB5UKALPYFP59') # => true (alfanumérico) +validator.is_valid('1QB5UKALpyfp59') # => false (padrão é case-sensitive) +validator.is_valid('1QB5UKALpyfp59', case_sensitive: false) # => true -validator.is_valid('96206256120884') # => true (numérico) +validator.is_valid('96206256120884') # => true (numérico) validator.is_valid('1QB5UKALPYFP59', type: 'numeric') # => false (letras removidas → comprimento ≠ 14) ``` @@ -61,9 +61,9 @@ Helper funcional: ```ruby require 'cnpj-val' -CnpjVal.cnpj_val('98765432000198') # => true -CnpjVal.cnpj_val('98.765.432/0001-98') # => true -CnpjVal.cnpj_val('98765432000199') # => false +CnpjVal.cnpj_val('98765432000198') # => true +CnpjVal.cnpj_val('98.765.432/0001-98') # => true +CnpjVal.cnpj_val('98765432000199') # => false ``` ## Utilização @@ -89,7 +89,11 @@ validator = CnpjVal::CnpjValidator.new(type: 'numeric') validator.is_valid('98.765.432/0001-98') # => true validator.is_valid('1QB5UKALPYFP59') # => false (letras removidas → comprimento ≠ 14) -validator.is_valid('1QB5UKALpyfp59', type: 'alphanumeric', case_sensitive: false) # => true +validator.is_valid( # => true + '1QB5UKALpyfp59', + type: 'alphanumeric', + case_sensitive: false +) ``` Padrões na instância; sobrescrita por chamada: @@ -99,9 +103,9 @@ require 'cnpj-val' validator = CnpjVal::CnpjValidator.new(case_sensitive: false) -validator.is_valid('1qb5ukalpyfp59') # => true (padrões da instância) -validator.is_valid('1qb5ukalpyfp59', case_sensitive: true) # só nesta chamada: false -validator.is_valid('1qb5ukalpyfp59') # => true de novo +validator.is_valid('1qb5ukalpyfp59') # => true (padrões da instância) +validator.is_valid('1qb5ukalpyfp59', case_sensitive: true) # só nesta chamada: false +validator.is_valid('1qb5ukalpyfp59') # => true de novo ``` ### `CnpjVal::CnpjValidatorOptions` @@ -115,10 +119,10 @@ Armazena configurações do validador (`case_sensitive`, `type`). Construa com u require 'cnpj-val' options = CnpjVal::CnpjValidatorOptions.new(case_sensitive: false, type: 'numeric') -options.case_sensitive # => false -options.type # => "numeric" -options.set({ type: 'alphanumeric' }) # mescla e retorna self -options.all # => snapshot congelado das opções atuais +options.case_sensitive # => false +options.type # => "numeric" +options.set({ type: 'alphanumeric' }) # mescla e retorna self +options.all # => snapshot congelado das opções atuais ``` ### Helper funcional @@ -128,13 +132,13 @@ options.all # => snapshot congelado das opções atuais ```ruby require 'cnpj-val' -CnpjVal.cnpj_val('98765432000198') # => true -CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true -CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false -CnpjVal.cnpj_val('1QB5UKALpyfp59', { # forma com Hash +CnpjVal.cnpj_val('98765432000198') # => true +CnpjVal.cnpj_val('1QB5UKALpyfp59', case_sensitive: false) # => true +CnpjVal.cnpj_val('1QB5UKALPYFP59', type: 'numeric') # => false +CnpjVal.cnpj_val('1QB5UKALpyfp59', { # forma com Hash type: 'alphanumeric', case_sensitive: false, -}) # => true +}) # => true ``` ### Formatos de entrada @@ -146,8 +150,8 @@ CnpjVal.cnpj_val('1QB5UKALpyfp59', { # forma com Hash ```ruby require 'cnpj-val' -CnpjVal.cnpj_val(['1', 'Q', 'B', '5', 'U', 'K', 'A', 'L', 'P', 'Y', 'F', 'P', '5', '9']) # => true -CnpjVal.cnpj_val(['1Q.B5U', 'KAL', 'PYFP-59']) # => true +CnpjVal.cnpj_val(['1', 'Q', 'B', '5', 'U', 'K', 'A', 'L', 'P', 'Y', 'F', 'P', '5', '9']) # => true +CnpjVal.cnpj_val(['1Q.B5U', 'KAL', 'PYFP-59']) # => true ``` ### Opções de validação @@ -226,8 +230,8 @@ rescue CnpjVal::DomainError - **Exemplo:** ```ruby -CnpjVal.cnpj_val(12_345_678_000_198) # levanta CnpjVal::TypeMismatchError -CnpjVal.cnpj_val('98765432000198', type: 123) # levanta CnpjVal::TypeMismatchError +CnpjVal.cnpj_val(12_345_678_000_198) # levanta CnpjVal::TypeMismatchError +CnpjVal.cnpj_val('98765432000198', type: 123) # levanta CnpjVal::TypeMismatchError ``` - **Como resgatá-lo:** @@ -274,7 +278,7 @@ rescue ArgumentError - **Exemplo:** ```ruby -CnpjVal.cnpj_val('98765432000198', type: 'invalid') # levanta CnpjVal::ValidationError +CnpjVal.cnpj_val('98765432000198', type: 'invalid') # levanta CnpjVal::ValidationError ``` - **Como resgatá-lo:** diff --git a/packages/cpf-dv/README.md b/packages/cpf-dv/README.md index 84f5e61..22e050a 100644 --- a/packages/cpf-dv/README.md +++ b/packages/cpf-dv/README.md @@ -65,10 +65,10 @@ require 'cpf-dv' check_digits = CpfDV::CpfCheckDigits.new('054496519') -check_digits.first # => '1' -check_digits.second # => '0' -check_digits.both # => '10' -check_digits.cpf # => '05449651910' +check_digits.first # => '1' +check_digits.second # => '0' +check_digits.both # => '10' +check_digits.cpf # => '05449651910' ``` @@ -162,7 +162,7 @@ rescue CpfDV::DomainError - **Example:** ```ruby -CpfDV::CpfCheckDigits.new(12_345_678_901) # raises CpfDV::TypeMismatchError +CpfDV::CpfCheckDigits.new(12_345_678_901) # raises CpfDV::TypeMismatchError ``` - **How to rescue it:** @@ -183,7 +183,7 @@ rescue TypeError - **Example:** ```ruby -CpfDV::CpfCheckDigits.new('12345678') # raises CpfDV::InvalidLengthError +CpfDV::CpfCheckDigits.new('12345678') # raises CpfDV::InvalidLengthError ``` - **How to rescue it:** @@ -204,7 +204,7 @@ rescue CpfDV::DomainError - **Example:** ```ruby -CpfDV::CpfCheckDigits.new('111111111') # raises CpfDV::ValidationError +CpfDV::CpfCheckDigits.new('111111111') # raises CpfDV::ValidationError ``` - **How to rescue it:** diff --git a/packages/cpf-dv/README.pt.md b/packages/cpf-dv/README.pt.md index 5fb3980..60d9cac 100644 --- a/packages/cpf-dv/README.pt.md +++ b/packages/cpf-dv/README.pt.md @@ -42,10 +42,10 @@ require 'cpf-dv' check_digits = CpfDV::CpfCheckDigits.new('054496519') -check_digits.first # => '1' -check_digits.second # => '0' -check_digits.both # => '10' -check_digits.cpf # => '05449651910' +check_digits.first # => '1' +check_digits.second # => '0' +check_digits.both # => '10' +check_digits.cpf # => '05449651910' ``` ## Utilização @@ -133,7 +133,7 @@ rescue CpfDV::DomainError - **Exemplo:** ```ruby -CpfDV::CpfCheckDigits.new(12_345_678_901) # levanta CpfDV::TypeMismatchError +CpfDV::CpfCheckDigits.new(12_345_678_901) # levanta CpfDV::TypeMismatchError ``` - **Como resgatar:** @@ -154,7 +154,7 @@ rescue TypeError - **Exemplo:** ```ruby -CpfDV::CpfCheckDigits.new('12345678') # levanta CpfDV::InvalidLengthError +CpfDV::CpfCheckDigits.new('12345678') # levanta CpfDV::InvalidLengthError ``` - **Como resgatar:** @@ -175,7 +175,7 @@ rescue CpfDV::DomainError - **Exemplo:** ```ruby -CpfDV::CpfCheckDigits.new('111111111') # levanta CpfDV::ValidationError +CpfDV::CpfCheckDigits.new('111111111') # levanta CpfDV::ValidationError ``` - **Como resgatar:** diff --git a/packages/cpf-dv/cpf-dv.gemspec b/packages/cpf-dv/cpf-dv.gemspec index 71f4f8b..d7c1a18 100644 --- a/packages/cpf-dv/cpf-dv.gemspec +++ b/packages/cpf-dv/cpf-dv.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.1' spec.metadata['source_code_uri'] = spec.homepage spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] spec.add_dependency 'lacus-utils', '>= 1.1.0', '< 2.0.0' end diff --git a/packages/cpf-fmt/README.md b/packages/cpf-fmt/README.md index 60c95b3..bb0b413 100644 --- a/packages/cpf-fmt/README.md +++ b/packages/cpf-fmt/README.md @@ -67,9 +67,9 @@ require 'cpf-fmt' cpf = '03603568195' -CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" -CpfFmt.cpf_fmt(cpf, hidden: true) # => "036.***.***-**" -CpfFmt.cpf_fmt( # => "036035681_95" +CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" +CpfFmt.cpf_fmt(cpf, hidden: true) # => "036.***.***-**" +CpfFmt.cpf_fmt( # => "036035681_95" cpf, dot_key: '', dash_key: '_' @@ -115,14 +115,14 @@ require 'cpf-fmt' cpf = '03603568195' -CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" -CpfFmt.cpf_fmt(cpf, hidden: true) # masked with defaults -CpfFmt.cpf_fmt( # => "036035681_95" +CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" +CpfFmt.cpf_fmt(cpf, hidden: true) # masked with defaults +CpfFmt.cpf_fmt( # => "036035681_95" cpf, dot_key: '', dash_key: '_' ) -CpfFmt.cpf_fmt(cpf, { # Hash form +CpfFmt.cpf_fmt(cpf, { # Hash form hidden: true, hidden_key: '#' }) @@ -137,7 +137,7 @@ formatter = CpfFmt::CpfFormatter.new cpf = '12345678910' formatter.format(cpf) # => "123.456.789-10" -formatter.format( # => "123.###.###-##" +formatter.format( # => "123.###.###-##" cpf, hidden: true, hidden_key: '#', @@ -154,9 +154,9 @@ require 'cpf-fmt' formatter = CpfFmt::CpfFormatter.new(hidden: true) cpf = '12345678910' -formatter.format(cpf) # uses instance masking -formatter.format(cpf, hidden: false) # this call only: unmasked -formatter.format(cpf) # back to instance defaults +formatter.format(cpf) # uses instance masking +formatter.format(cpf, hidden: false) # this call only: unmasked +formatter.format(cpf) # back to instance defaults ``` Array input: @@ -274,7 +274,7 @@ rescue CpfFmt::DomainError - **Example:** ```ruby -CpfFmt::CpfFormatter.new.format(12_345) # raises CpfFmt::TypeMismatchError +CpfFmt::CpfFormatter.new.format(12_345) # raises CpfFmt::TypeMismatchError ``` - **How to rescue it:** @@ -298,10 +298,10 @@ rescue TypeError CpfFmt::CpfFormatter.new.format( 'short', on_fail: ->(_value, error) { - error # => # (a DomainError) + error # => # (a DomainError) 'invalid' } -) # => "invalid" +) # => "invalid" ``` @@ -349,7 +349,7 @@ rescue ArgumentError - **Example:** ```ruby -CpfFmt::CpfFormatterOptions.new(hidden_start: 11) # raises CpfFmt::OutOfRangeError +CpfFmt::CpfFormatterOptions.new(hidden_start: 11) # raises CpfFmt::OutOfRangeError ``` - **How to rescue it:** @@ -370,7 +370,7 @@ rescue CpfFmt::DomainError - **Example:** ```ruby -CpfFmt::CpfFormatterOptions.new(dot_key: 'å') # raises CpfFmt::ValidationError +CpfFmt::CpfFormatterOptions.new(dot_key: 'å') # raises CpfFmt::ValidationError ``` - **How to rescue it:** diff --git a/packages/cpf-fmt/README.pt.md b/packages/cpf-fmt/README.pt.md index 2f25323..8120173 100644 --- a/packages/cpf-fmt/README.pt.md +++ b/packages/cpf-fmt/README.pt.md @@ -54,9 +54,9 @@ require 'cpf-fmt' cpf = '03603568195' -CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" -CpfFmt.cpf_fmt(cpf, hidden: true) # => "036.***.***-**" -CpfFmt.cpf_fmt( # => "036035681_95" +CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" +CpfFmt.cpf_fmt(cpf, hidden: true) # => "036.***.***-**" +CpfFmt.cpf_fmt( # => "036035681_95" cpf, dot_key: '', dash_key: '_' @@ -102,14 +102,14 @@ require 'cpf-fmt' cpf = '03603568195' -CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" -CpfFmt.cpf_fmt(cpf, hidden: true) # mascarado com padrões -CpfFmt.cpf_fmt( # => "036035681_95" +CpfFmt.cpf_fmt(cpf) # => "036.035.681-95" +CpfFmt.cpf_fmt(cpf, hidden: true) # mascarado com padrões +CpfFmt.cpf_fmt( # => "036035681_95" cpf, dot_key: '', dash_key: '_' ) -CpfFmt.cpf_fmt(cpf, { # forma com Hash +CpfFmt.cpf_fmt(cpf, { # forma com Hash hidden: true, hidden_key: '#' }) @@ -124,7 +124,7 @@ formatter = CpfFmt::CpfFormatter.new cpf = '12345678910' formatter.format(cpf) # => "123.456.789-10" -formatter.format( # => "123.###.###-##" +formatter.format( # => "123.###.###-##" cpf, hidden: true, hidden_key: '#', @@ -141,9 +141,9 @@ require 'cpf-fmt' formatter = CpfFmt::CpfFormatter.new(hidden: true) cpf = '12345678910' -formatter.format(cpf) # usa mascaramento da instância -formatter.format(cpf, hidden: false) # só nesta chamada: sem máscara -formatter.format(cpf) # volta aos padrões da instância +formatter.format(cpf) # usa mascaramento da instância +formatter.format(cpf, hidden: false) # só nesta chamada: sem máscara +formatter.format(cpf) # volta aos padrões da instância ``` Entrada em array: @@ -153,7 +153,7 @@ require 'cpf-fmt' formatter = CpfFmt::CpfFormatter.new -formatter.format([ # => "123.456.789-10" +formatter.format([ # => "123.456.789-10" '123', '456', '789', @@ -261,7 +261,7 @@ rescue CpfFmt::DomainError - **Exemplo:** ```ruby -CpfFmt::CpfFormatter.new.format(12_345) # levanta CpfFmt::TypeMismatchError +CpfFmt::CpfFormatter.new.format(12_345) # levanta CpfFmt::TypeMismatchError ``` - **Como resgatar:** @@ -285,10 +285,10 @@ rescue TypeError CpfFmt::CpfFormatter.new.format( 'short', on_fail: ->(_value, error) { - error # => # (um DomainError) + error # => # (um DomainError) 'invalid' } -) # => "invalid" +) # => "invalid" ``` - **Como resgatar:** Trate dentro do `on_fail` (caso típico), ou resgate se você o reerguer: @@ -335,7 +335,7 @@ rescue ArgumentError - **Exemplo:** ```ruby -CpfFmt::CpfFormatterOptions.new(hidden_start: 11) # levanta CpfFmt::OutOfRangeError +CpfFmt::CpfFormatterOptions.new(hidden_start: 11) # levanta CpfFmt::OutOfRangeError ``` - **Como resgatar:** @@ -356,7 +356,7 @@ rescue CpfFmt::DomainError - **Exemplo:** ```ruby -CpfFmt::CpfFormatterOptions.new(dot_key: 'å') # levanta CpfFmt::ValidationError +CpfFmt::CpfFormatterOptions.new(dot_key: 'å') # levanta CpfFmt::ValidationError ``` - **Como resgatar:** diff --git a/packages/cpf-fmt/cpf-fmt.gemspec b/packages/cpf-fmt/cpf-fmt.gemspec index 27db211..8d54c7a 100644 --- a/packages/cpf-fmt/cpf-fmt.gemspec +++ b/packages/cpf-fmt/cpf-fmt.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.1' spec.metadata['source_code_uri'] = spec.homepage spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] spec.add_dependency 'lacus-utils', '>= 1.1.0', '< 2.0.0' end diff --git a/packages/cpf-gen/README.md b/packages/cpf-gen/README.md index f7906ea..584b5b5 100644 --- a/packages/cpf-gen/README.md +++ b/packages/cpf-gen/README.md @@ -54,12 +54,12 @@ require 'cpf-gen' ```ruby require 'cpf-gen' -CpfGen.cpf_gen # => e.g. "47844241055" (11-digit numeric) +CpfGen.cpf_gen # => e.g. "47844241055" (11-digit numeric) -CpfGen.cpf_gen(format: true) # => e.g. "005.265.352-88" +CpfGen.cpf_gen(format: true) # => e.g. "005.265.352-88" -CpfGen.cpf_gen(prefix: '528250911') # => e.g. "52825091138" -CpfGen.cpf_gen( # => e.g. "528.250.911-38" +CpfGen.cpf_gen(prefix: '528250911') # => e.g. "52825091138" +CpfGen.cpf_gen( # => e.g. "528.250.911-38" prefix: '528250911', format: true ) @@ -104,9 +104,9 @@ require 'cpf-gen' generator = CpfGen::CpfGenerator.new(format: true) -generator.generate # => e.g. "005.265.352-88" -generator.generate(prefix: '123456') # override for this call only -generator.options # current default options (CpfGen::CpfGeneratorOptions) +generator.generate # => e.g. "005.265.352-88" +generator.generate(prefix: '123456') # override for this call only +generator.options # current default options (CpfGen::CpfGeneratorOptions) ``` - **`initialize(options = nil, **keywords)`**: Optional default options. When `options` is given (a `CpfGen::CpfGeneratorOptions` instance or a `Hash`) alone, it determines the default options; a `CpfGen::CpfGeneratorOptions` instance is stored by reference (mutating it later affects future `generate` calls that do not pass per-call options), while a `Hash` builds a new instance. When `options` is omitted (`nil`), the default options are built exclusively from the keyword arguments (`format:`, `prefix:`). Passing `options` together with any non-`nil` keyword raises `InvalidArgumentCombinationError` instead of silently ignoring the keywords. @@ -120,9 +120,9 @@ require 'cpf-gen' generator = CpfGen::CpfGenerator.new(format: true) -generator.generate # formatted CPF -generator.generate(format: false) # this call only: unformatted -generator.generate # formatted again (instance defaults preserved) +generator.generate # formatted CPF +generator.generate(format: false) # this call only: unformatted +generator.generate # formatted again (instance defaults preserved) ``` ### `CpfGen::CpfGeneratorOptions` (class) @@ -136,10 +136,10 @@ options = CpfGen::CpfGeneratorOptions.new( prefix: '123456', format: true ) -options.prefix # => "123456" -options.format # => true -options.set(format: false) # merge and return self -options.all # => { format: false, prefix: "123456" } +options.prefix # => "123456" +options.format # => true +options.set(format: false) # merge and return self +options.all # => { format: false, prefix: "123456" } # Resetting a property to its default value requires the literal constant — # a bare `nil` on a setter raises TypeMismatchError: @@ -220,7 +220,7 @@ rescue CpfGen::DomainError - **Example:** ```ruby -CpfGen.cpf_gen(prefix: 123) # raises CpfGen::TypeMismatchError +CpfGen.cpf_gen(prefix: 123) # raises CpfGen::TypeMismatchError ``` - **How to rescue it:** @@ -267,8 +267,8 @@ rescue ArgumentError - **Example:** ```ruby -CpfGen.cpf_gen(prefix: '000000000') # raises CpfGen::ValidationError -CpfGen.cpf_gen(prefix: '999999999') # raises CpfGen::ValidationError +CpfGen.cpf_gen(prefix: '000000000') # raises CpfGen::ValidationError +CpfGen.cpf_gen(prefix: '999999999') # raises CpfGen::ValidationError ``` - **How to rescue it:** diff --git a/packages/cpf-gen/README.pt.md b/packages/cpf-gen/README.pt.md index 7de3618..55569b9 100644 --- a/packages/cpf-gen/README.pt.md +++ b/packages/cpf-gen/README.pt.md @@ -39,12 +39,12 @@ require 'cpf-gen' ```ruby require 'cpf-gen' -CpfGen.cpf_gen # => ex.: "47844241055" (11 dígitos numéricos) +CpfGen.cpf_gen # => ex.: "47844241055" (11 dígitos numéricos) -CpfGen.cpf_gen(format: true) # => ex.: "005.265.352-88" +CpfGen.cpf_gen(format: true) # => ex.: "005.265.352-88" -CpfGen.cpf_gen(prefix: '528250911') # => ex.: "52825091138" -CpfGen.cpf_gen( # => ex.: "528.250.911-38" +CpfGen.cpf_gen(prefix: '528250911') # => ex.: "52825091138" +CpfGen.cpf_gen( # => ex.: "528.250.911-38" prefix: '528250911', format: true ) @@ -89,9 +89,9 @@ require 'cpf-gen' generator = CpfGen::CpfGenerator.new(format: true) -generator.generate # => ex.: "005.265.352-88" -generator.generate(prefix: '123456') # sobrescrita apenas nesta chamada -generator.options # opções padrão atuais (CpfGen::CpfGeneratorOptions) +generator.generate # => ex.: "005.265.352-88" +generator.generate(prefix: '123456') # sobrescrita apenas nesta chamada +generator.options # opções padrão atuais (CpfGen::CpfGeneratorOptions) ``` - **`initialize(options = nil, **keywords)`**: Opções padrão opcionais. Quando `options` é fornecido isoladamente (instância de `CpfGen::CpfGeneratorOptions` ou `Hash`), ele determina as opções padrão; uma instância de `CpfGen::CpfGeneratorOptions` é armazenada por referência (mutações posteriores afetam futuras chamadas de `generate` que não passarem opções por chamada), enquanto um `Hash` cria uma nova instância. Quando `options` é omitido (`nil`), as opções padrão são construídas exclusivamente a partir dos argumentos nomeados (`format:`, `prefix:`). Passar `options` junto com qualquer argumento nomeado não `nil` gera `InvalidArgumentCombinationError`, em vez de ignorar os argumentos nomeados silenciosamente. @@ -105,9 +105,9 @@ require 'cpf-gen' generator = CpfGen::CpfGenerator.new(format: true) -generator.generate # CPF formatado -generator.generate(format: false) # somente nesta chamada: sem formato -generator.generate # volta ao padrão da instância +generator.generate # CPF formatado +generator.generate(format: false) # somente nesta chamada: sem formato +generator.generate # volta ao padrão da instância ``` ### `CpfGen::CpfGeneratorOptions` (classe) @@ -121,10 +121,10 @@ options = CpfGen::CpfGeneratorOptions.new( prefix: '123456', format: true ) -options.prefix # => "123456" -options.format # => true -options.set(format: false) # mescla e retorna self -options.all # => { format: false, prefix: "123456" } +options.prefix # => "123456" +options.format # => true +options.set(format: false) # mescla e retorna self +options.all # => { format: false, prefix: "123456" } # Redefinir uma propriedade ao seu valor padrão exige a constante literal — # um `nil` direto no setter lança TypeMismatchError: @@ -205,7 +205,7 @@ rescue CpfGen::DomainError - **Exemplo:** ```ruby -CpfGen.cpf_gen(prefix: 123) # levanta CpfGen::TypeMismatchError +CpfGen.cpf_gen(prefix: 123) # levanta CpfGen::TypeMismatchError ``` - **Como resgatar:** @@ -252,8 +252,8 @@ rescue ArgumentError - **Exemplo:** ```ruby -CpfGen.cpf_gen(prefix: '000000000') # levanta CpfGen::ValidationError -CpfGen.cpf_gen(prefix: '999999999') # levanta CpfGen::ValidationError +CpfGen.cpf_gen(prefix: '000000000') # levanta CpfGen::ValidationError +CpfGen.cpf_gen(prefix: '999999999') # levanta CpfGen::ValidationError ``` - **Como resgatar:** diff --git a/packages/cpf-gen/cpf-gen.gemspec b/packages/cpf-gen/cpf-gen.gemspec index 54130b9..1c67dc4 100644 --- a/packages/cpf-gen/cpf-gen.gemspec +++ b/packages/cpf-gen/cpf-gen.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.1' spec.metadata['source_code_uri'] = spec.homepage spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] spec.add_dependency 'cpf-dv', '>= 1.0.0', '< 1.1.0' spec.add_dependency 'lacus-utils', '>= 1.1.0', '< 2.0.0' diff --git a/packages/cpf-utilities/cpf-utilities.gemspec b/packages/cpf-utilities/cpf-utilities.gemspec index 45a95e8..99a273a 100644 --- a/packages/cpf-utilities/cpf-utilities.gemspec +++ b/packages/cpf-utilities/cpf-utilities.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |spec| spec.license = 'MIT' spec.required_ruby_version = '>= 3.1' spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] spec.add_dependency 'cpf-fmt', '>= 0' spec.add_dependency 'cpf-gen', '>= 0' diff --git a/packages/cpf-val/README.md b/packages/cpf-val/README.md index 1c1d148..acbff53 100644 --- a/packages/cpf-val/README.md +++ b/packages/cpf-val/README.md @@ -54,10 +54,10 @@ require 'cpf-val' validator = CpfVal::CpfValidator.new -validator.is_valid('12345678909') # => true -validator.is_valid('123.456.789-09') # => true -validator.is_valid('12345678910') # => false (invalid check digits) -validator.is_valid('00000000000') # => false (repeated digits) +validator.is_valid('12345678909') # => true +validator.is_valid('123.456.789-09') # => true +validator.is_valid('12345678910') # => false (invalid check digits) +validator.is_valid('00000000000') # => false (repeated digits) ``` Functional helper: @@ -88,11 +88,11 @@ require 'cpf-val' validator = CpfVal::CpfValidator.new -validator.is_valid('123.456.789-09') # => true -validator.is_valid('12345678909') # => true -validator.is_valid(['123', '456', '789', '09']) # => true -validator.is_valid('12345678910') # => false (invalid check digits) -validator.is_valid('11111111111') # => false (repeated digits) +validator.is_valid('123.456.789-09') # => true +validator.is_valid('12345678909') # => true +validator.is_valid(['123', '456', '789', '09']) # => true +validator.is_valid('12345678910') # => false (invalid check digits) +validator.is_valid('11111111111') # => false (repeated digits) ``` ### Functional helper @@ -116,8 +116,8 @@ CpfVal.cpf_val('11144477736') # => false ```ruby require 'cpf-val' -CpfVal.cpf_val(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '9']) # => true -CpfVal.cpf_val(['123.456', '789-09']) # => true +CpfVal.cpf_val(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '9']) # => true +CpfVal.cpf_val(['123.456', '789-09']) # => true ``` ### Error handling diff --git a/packages/cpf-val/README.pt.md b/packages/cpf-val/README.pt.md index 1e62d14..2c76577 100644 --- a/packages/cpf-val/README.pt.md +++ b/packages/cpf-val/README.pt.md @@ -41,10 +41,10 @@ require 'cpf-val' validator = CpfVal::CpfValidator.new -validator.is_valid('12345678909') # => true -validator.is_valid('123.456.789-09') # => true -validator.is_valid('12345678910') # => false (dígitos verificadores inválidos) -validator.is_valid('00000000000') # => false (dígitos repetidos) +validator.is_valid('12345678909') # => true +validator.is_valid('123.456.789-09') # => true +validator.is_valid('12345678910') # => false (dígitos verificadores inválidos) +validator.is_valid('00000000000') # => false (dígitos repetidos) ``` Helper funcional: @@ -75,11 +75,11 @@ require 'cpf-val' validator = CpfVal::CpfValidator.new -validator.is_valid('123.456.789-09') # => true -validator.is_valid('12345678909') # => true -validator.is_valid(['123', '456', '789', '09']) # => true -validator.is_valid('12345678910') # => false (dígitos verificadores inválidos) -validator.is_valid('11111111111') # => false (dígitos repetidos) +validator.is_valid('123.456.789-09') # => true +validator.is_valid('12345678909') # => true +validator.is_valid(['123', '456', '789', '09']) # => true +validator.is_valid('12345678910') # => false (dígitos verificadores inválidos) +validator.is_valid('11111111111') # => false (dígitos repetidos) ``` ### Helper funcional @@ -103,8 +103,8 @@ CpfVal.cpf_val('11144477736') # => false ```ruby require 'cpf-val' -CpfVal.cpf_val(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '9']) # => true -CpfVal.cpf_val(['123.456', '789-09']) # => true +CpfVal.cpf_val(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '9']) # => true +CpfVal.cpf_val(['123.456', '789-09']) # => true ``` ### Tratamento de erros diff --git a/packages/cpf-val/cpf-val.gemspec b/packages/cpf-val/cpf-val.gemspec index 0426a20..53648f4 100644 --- a/packages/cpf-val/cpf-val.gemspec +++ b/packages/cpf-val/cpf-val.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.1' spec.metadata['source_code_uri'] = spec.homepage spec.metadata['rubygems_mfa_required'] = 'true' - spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md'].select { |f| File.file?(f) } + spec.files = Dir['src/**/*'] + ['LICENSE', 'README.md', 'README.pt.md', 'CHANGELOG.md'] spec.require_paths = ['src'] spec.add_dependency 'cpf-dv', '>= 1.0.0', '< 1.1.0' spec.add_dependency 'lacus-utils', '>= 1.1.0', '< 2.0.0'