cpf-gen 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c10b8db3011cda5c04f901eaa4857292f68b53f6284c6aeb3d5cc8531dcb6b39
4
- data.tar.gz: 8eccf9c46f05610d4542dd76f9287e28001f628877d7769675efc3783a0d83f0
3
+ metadata.gz: 2298bdcc13739540003bba7d1448506b05ecdcdb0883af12fdcd429fbdc6cfea
4
+ data.tar.gz: 10b903a922490bb5835e63a69a456d7a15b352868929b9437bb9b6f0b70e10e5
5
5
  SHA512:
6
- metadata.gz: 1b5cb7f1df8c798cafd49075644ad01fe8617be794eb67100203c58badaf607170b11736683dee2c44534c7d85d2f7a4222936e864061e0434d7e52e8d8ec048
7
- data.tar.gz: 4567e17717563780cb031e96624213e95323a0a364999a17a06fd34c127e7bd0248cbfb82a42b4595b66589a25a56c6f5b5d491c085dcce7be9f3a61cac0e342
6
+ metadata.gz: e6b2fc018dd214b637bb2d107c1ea850ae2bfefbec3d12b4e40fcda0d8d783b08bbf31b52c9669137d83649a8f43329399fa3dc5cdf53efb59565c5e7fb9fe7e
7
+ data.tar.gz: 616ea0a6c598b7b628d3c3bc0af9ca7cf26211f6d6ce816222b1b99088478feb900299cc8f8134daf239f42805468754ce4e06be92b6df30b751e70c01688105
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Julio L. Muller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,350 @@
1
+ ![cpf-gen for Ruby](https://br-utils.vercel.app/img/cover_cpf-gen.jpg)
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/cpf-gen)](https://rubygems.org/gems/cpf-gen)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/cpf-gen)](https://rubygems.org/gems/cpf-gen)
5
+ [![Ruby Version](https://img.shields.io/gem/rv/cpf-gen)](https://www.ruby-lang.org/)
6
+ [![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)
7
+ [![Last Update Date](https://img.shields.io/github/last-commit/LacusSolutions/br-utils-ruby)](https://github.com/LacusSolutions/br-utils-ruby)
8
+ [![Project License](https://img.shields.io/github/license/LacusSolutions/br-utils-ruby)](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE)
9
+
10
+ > 🌎 [Acessar documentação em português](./README.pt.md)
11
+
12
+ A Ruby utility to generate valid CPF (Brazilian Individual's Taxpayer ID) values.
13
+
14
+ ## Ruby Support
15
+
16
+ | ![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) |
17
+ | --- | --- | --- |
18
+ | Passing ✔ | Passing ✔ | Passing ✔ |
19
+
20
+ Requires Ruby **≥ 3.1** (see `required_ruby_version` in the gemspec).
21
+
22
+ ## Features
23
+
24
+ - ✅ **Numeric CPF**: Generates 11-digit numeric CPF values with valid check digits
25
+ - ✅ **Optional prefix**: Provide 0–9 digits to fix the start of the CPF and generate the rest with valid check digits
26
+ - ✅ **Formatting**: Option to return the standard formatted string (`000.000.000-00`)
27
+ - ✅ **Reusable generator**: `CpfGen::CpfGenerator` class with default options and per-call overrides
28
+ - ✅ **Keyword overrides**: Pass `format:` and `prefix:` on `cpf_gen`, `CpfGenerator#generate`, and constructors
29
+ - ✅ **Minimal dependencies**: Only [`cpf-dv`](https://rubygems.org/gems/cpf-dv) and [`lacus-utils`](https://rubygems.org/gems/lacus-utils)
30
+ - ✅ **Error handling**: API misuse vs domain errors with a `CpfGen::Error` marker for library-wide rescue
31
+
32
+ ## Installation
33
+
34
+ Install the gem directly:
35
+
36
+ ```bash
37
+ gem install cpf-gen
38
+ ```
39
+
40
+ Or add it to your `Gemfile` and run `bundle install`:
41
+
42
+ ```ruby
43
+ gem 'cpf-gen'
44
+ ```
45
+
46
+ ## Require
47
+
48
+ ```ruby
49
+ require 'cpf-gen'
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```ruby
55
+ require 'cpf-gen'
56
+
57
+ CpfGen.cpf_gen # => e.g. "47844241055" (11-digit numeric)
58
+
59
+ CpfGen.cpf_gen(format: true) # => e.g. "005.265.352-88"
60
+
61
+ CpfGen.cpf_gen(prefix: '528250911') # => e.g. "52825091138"
62
+ CpfGen.cpf_gen( # => e.g. "528.250.911-38"
63
+ prefix: '528250911',
64
+ format: true
65
+ )
66
+ ```
67
+
68
+ Options can also be passed as a `Hash`:
69
+
70
+ ```ruby
71
+ CpfGen.cpf_gen({ format: true, prefix: '528250911' })
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ The main entry points are the module helper `CpfGen.cpf_gen`, the class `CpfGen::CpfGenerator`, and the options class `CpfGen::CpfGeneratorOptions`.
77
+
78
+ ### Generator options
79
+
80
+ All options are optional:
81
+
82
+ | Option | Type | Default | Description |
83
+ |--------|------|---------|-------------|
84
+ | `format` | `Boolean` | `false` | When truthy, return the generated CPF in standard format (`000.000.000-00`). Non-boolean values are coerced (`false`, `''`, and `0` become `false`; other values become truthy). |
85
+ | `prefix` | `String` | `''` | Partial start string (0–9 digits). Only digits are kept; missing characters are generated randomly and check digits are computed. Prefixes longer than 9 digits are truncated silently. |
86
+
87
+ Prefix rules: the base (first 9 digits) cannot be all zeros; 9 repeated digits (e.g. `999999999`) are not allowed. Prefixes shorter than 9 digits are never rejected by these rules (e.g. `"00000000"` and `"11111111"` are allowed).
88
+
89
+ `nil` is accepted as a keyword argument on `cpf_gen`, `CpfGenerator.new`, `CpfGenerator#generate`, and `CpfGeneratorOptions.new`/`#set` — it simply means "no override for this option". It is **not** accepted by the `CpfGeneratorOptions` property setters (`options.format = value`, `options.prefix = value`): calling a setter with `nil` directly raises `CpfGen::TypeMismatchError`. To reset a property to its default value through a setter, pass the literal constant instead, e.g. `options.format = CpfGen::CpfGeneratorOptions::DEFAULT_FORMAT`.
90
+
91
+ ### `CpfGen.cpf_gen` (helper)
92
+
93
+ Generates a valid CPF string. With no options, returns an 11-digit numeric CPF. This is a convenience wrapper around `CpfGen::CpfGenerator.new(...).generate`.
94
+
95
+ - **`options`** (optional): `CpfGen::CpfGeneratorOptions` instance, a `Hash` of option keys, or `nil`. See [Generator options](#generator-options).
96
+ - **`format`**, **`prefix`** (keyword arguments): Only used when `options` is omitted (`nil`). Passing `options` **and** any non-`nil` of these keywords at the same time raises `InvalidArgumentCombinationError` — the two ways of passing options are never merged.
97
+
98
+ ### `CpfGen::CpfGenerator` (class)
99
+
100
+ For reusable defaults or per-call overrides, use the class:
101
+
102
+ ```ruby
103
+ require 'cpf-gen'
104
+
105
+ generator = CpfGen::CpfGenerator.new(format: true)
106
+
107
+ generator.generate # => e.g. "005.265.352-88"
108
+ generator.generate(prefix: '123456') # override for this call only
109
+ generator.options # current default options (CpfGen::CpfGeneratorOptions)
110
+ ```
111
+
112
+ - **`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.
113
+ - **`generate(options = nil, **keywords)`**: Returns a valid CPF. `options` and the keyword arguments are never merged: a given `options` argument alone fully overrides the instance defaults for this call; otherwise, any given keyword overrides the instance defaults for this call. When neither is given, the instance defaults are used as-is. The instance defaults are never mutated by a per-call override. Passing `options` together with any non-`nil` keyword raises `InvalidArgumentCombinationError`.
114
+ - **`options`**: Reader returning the default options used when per-call options are not provided (same instance as used internally; mutating it affects future `generate` calls).
115
+
116
+ Default options on the instance; per-call overrides:
117
+
118
+ ```ruby
119
+ require 'cpf-gen'
120
+
121
+ generator = CpfGen::CpfGenerator.new(format: true)
122
+
123
+ generator.generate # formatted CPF
124
+ generator.generate(format: false) # this call only: unformatted
125
+ generator.generate # formatted again (instance defaults preserved)
126
+ ```
127
+
128
+ ### `CpfGen::CpfGeneratorOptions` (class)
129
+
130
+ Holds options (`format`, `prefix`) with validation and merge support:
131
+
132
+ ```ruby
133
+ require 'cpf-gen'
134
+
135
+ options = CpfGen::CpfGeneratorOptions.new(
136
+ prefix: '123456',
137
+ format: true
138
+ )
139
+ options.prefix # => "123456"
140
+ options.format # => true
141
+ options.set(format: false) # merge and return self
142
+ options.all # => { format: false, prefix: "123456" }
143
+
144
+ # Resetting a property to its default value requires the literal constant —
145
+ # a bare `nil` on a setter raises TypeMismatchError:
146
+ options.format = CpfGen::CpfGeneratorOptions::DEFAULT_FORMAT
147
+ ```
148
+
149
+ - **`initialize(*options, **keywords)`**: Every positional `options` argument (each a `Hash` or another `CpfGen::CpfGeneratorOptions` instance) is folded left to right — later arguments win — then the keyword arguments (`format:`, `prefix:`) are applied on top with the highest precedence. At every step, a `nil` value for a given key is ignored in favor of whatever was resolved so far. Any option still unresolved after that is set to its `DEFAULT_*` value.
150
+ - **`format`**, **`prefix`**: Accessors with setters; `prefix` is validated (zeroed base ID, repeated digits). The setters **never accept `nil`** — pass the matching `DEFAULT_*` constant (e.g. `CpfGeneratorOptions::DEFAULT_PREFIX`) to reset a property explicitly.
151
+ - **`set(*options, **keywords)`**: Updates multiple options at once, using the same fold-then-keywords, ignore-`nil` resolution as `initialize`. Any option left unresolved after merging keeps its **current** value on the instance (a partial update, not a re-initialization). Returns `self`.
152
+ - **`all`**: Shallow `Hash` copy of current options (`:format`, `:prefix`).
153
+
154
+ ## API
155
+
156
+ ### Exports
157
+
158
+ After `require 'cpf-gen'`:
159
+
160
+ - **`CpfGen.cpf_gen`**: `(options = nil, **keywords) -> String` — convenience helper.
161
+ - **`CpfGen::CpfGenerator`**: Class to generate CPF with optional default options and per-call overrides.
162
+ - **`CpfGen::CpfGeneratorOptions`**: Class holding options with validation and merge.
163
+ - **`CpfGen::CPF_LENGTH`**: `11` (constant).
164
+ - **`CpfGen::CPF_PREFIX_MAX_LENGTH`**: `9` (constant).
165
+ - **`CpfGen::VERSION`**: gem version string.
166
+ - **Errors**: `CpfGen::Error`, `CpfGen::DomainError`, `CpfGen::InvalidArgumentCombinationError`, `CpfGen::TypeMismatchError`, `CpfGen::ValidationError`.
167
+
168
+ ### Error handling
169
+
170
+ Errors fall into two categories:
171
+
172
+ | Category | Meaning |
173
+ |---|---|
174
+ | **API misuse** | The caller invoked the library incorrectly (wrong type for an option, or an invalid argument combination). |
175
+ | **Domain error** | The call was structurally correct, but a value violates a business rule (invalid `prefix`). |
176
+
177
+ Every custom error includes the `CpfGen::Error` marker module. Domain failures (`ValidationError`) inherit from `CpfGen::DomainError` (`RangeError`).
178
+
179
+ **Important:** passing both an `options` instance/`Hash` and any non-`nil` keyword argument raises `InvalidArgumentCombinationError`.
180
+
181
+ #### Summary
182
+
183
+ | Class | Inherits from | Category | Trigger condition |
184
+ |---|---|---|---|
185
+ | `CpfGen::InvalidArgumentCombinationError` | `ArgumentError` (+ `include Error`) | API misuse | Both an `options` instance/`Hash` and any non-`nil` keyword argument are passed at once |
186
+ | `CpfGen::TypeMismatchError` | `TypeError` (+ `include Error`) | API misuse | A generator option has the wrong data type |
187
+ | `CpfGen::ValidationError` | `CpfGen::DomainError` | Domain error | `prefix` is ineligible (zeroed base ID or 9 repeated digits) |
188
+
189
+ #### `CpfGen::Error` (marker module)
190
+
191
+ - **Inheritance:** module marker mixed into every library error via `include` (not a class).
192
+ - **Category:** N/A (rescue target only) — not a failure mode by itself.
193
+ - **When it is raised:** Never raised directly; included by every custom error the library raises.
194
+ - **Example:** N/A
195
+ - **How to rescue it:**
196
+
197
+ ```ruby
198
+ rescue CpfGen::Error
199
+ # everything this library raises
200
+ ```
201
+
202
+ #### `CpfGen::DomainError`
203
+
204
+ - **Inheritance:** `CpfGen::DomainError < RangeError` (includes `CpfGen::Error`)
205
+ - **Category:** Domain error — ancestor for all domain failures.
206
+ - **When it is raised:** Not raised directly; prefer raising a leaf subclass.
207
+ - **Example:** Prefer `raise CpfGen::ValidationError` over raising `DomainError` directly.
208
+ - **How to rescue it:**
209
+
210
+ ```ruby
211
+ rescue CpfGen::DomainError
212
+ # ValidationError and other DomainError subclasses
213
+ ```
214
+
215
+ #### `CpfGen::TypeMismatchError`
216
+
217
+ - **Inheritance:** `CpfGen::TypeMismatchError < TypeError` (includes `CpfGen::Error`)
218
+ - **Category:** API misuse — the caller passed a value of the wrong type.
219
+ - **When it is raised:** Raised when a generator option (`format` or `prefix`) has the wrong runtime type.
220
+ - **Example:**
221
+
222
+ ```ruby
223
+ CpfGen.cpf_gen(prefix: 123) # raises CpfGen::TypeMismatchError
224
+ ```
225
+
226
+ - **How to rescue it:**
227
+
228
+ ```ruby
229
+ rescue CpfGen::TypeMismatchError
230
+ # this library's type-contract violation
231
+
232
+ rescue TypeError
233
+ # native type errors, including this library's TypeMismatchError
234
+ ```
235
+
236
+ #### `CpfGen::InvalidArgumentCombinationError`
237
+
238
+ - **Inheritance:** `CpfGen::InvalidArgumentCombinationError < ArgumentError` (includes `CpfGen::Error`)
239
+ - **Category:** API misuse — the caller mixed mutually exclusive argument patterns.
240
+ - **When it is raised:** Raised when `CpfGenerator.new`, `#generate`, or `cpf_gen` receives both an `options` argument (instance or `Hash`) and any non-`nil` keyword argument at the same time.
241
+ - **Example:**
242
+
243
+ ```ruby
244
+ begin
245
+ CpfGen::CpfGenerator.new({ format: true }, prefix: '123')
246
+ rescue CpfGen::InvalidArgumentCombinationError => e
247
+ puts e.message
248
+ # Pass either an options instance/Hash to `options`, or keyword arguments (format:, prefix:), not both.
249
+ end
250
+ ```
251
+
252
+ - **How to rescue it:**
253
+
254
+ ```ruby
255
+ rescue CpfGen::InvalidArgumentCombinationError
256
+ # this library's invalid argument combination
257
+
258
+ rescue ArgumentError
259
+ # native argument errors, including this library's InvalidArgumentCombinationError
260
+ ```
261
+
262
+ #### `CpfGen::ValidationError`
263
+
264
+ - **Inheritance:** `CpfGen::ValidationError < CpfGen::DomainError < RangeError` (includes `CpfGen::Error`)
265
+ - **Category:** Domain error — a value fails a non-numeric, non-length domain rule.
266
+ - **When it is raised:** Raised when `prefix` is ineligible (zeroed base ID `"000000000"`, or 9 repeated digits such as `"999999999"`).
267
+ - **Example:**
268
+
269
+ ```ruby
270
+ CpfGen.cpf_gen(prefix: '000000000') # raises CpfGen::ValidationError
271
+ CpfGen.cpf_gen(prefix: '999999999') # raises CpfGen::ValidationError
272
+ ```
273
+
274
+ - **How to rescue it:**
275
+
276
+ ```ruby
277
+ rescue CpfGen::ValidationError
278
+ # this exact domain validation failure
279
+
280
+ rescue CpfGen::DomainError
281
+ # RangeError-rooted domain failures from this library
282
+ ```
283
+
284
+ #### Rescue granularity
285
+
286
+ ```ruby
287
+ # 1) Single native class — catches type misuse from this library (and other TypeErrors).
288
+ rescue TypeError
289
+ # CpfGen::TypeMismatchError and any other TypeError (library or not)
290
+
291
+ # 2) CpfGen::DomainError — catches business-rule violations under DomainError.
292
+ rescue CpfGen::DomainError
293
+ # CpfGen::ValidationError and other DomainError subclasses
294
+
295
+ # 3) CpfGen::Error — catches everything the library raises.
296
+ rescue CpfGen::Error
297
+ # every custom error that includes CpfGen::Error
298
+
299
+ # 4) Specific leaf class — catches only that exact failure mode.
300
+ rescue CpfGen::ValidationError
301
+ # only CpfGen::ValidationError
302
+ ```
303
+
304
+ Notable attributes:
305
+
306
+ - `TypeMismatchError`: `option_name`, `actual_input`, `actual_type`, `expected_type`
307
+ - `ValidationError`: `option_name`, `actual_input`, `reason` (prefix failures); `expected_values` is always `nil` for CPF
308
+
309
+ Property setters never accept `nil` directly — pass the matching `DEFAULT_*` constant to reset:
310
+
311
+ ```ruby
312
+ options = CpfGen::CpfGeneratorOptions.new
313
+ begin
314
+ options.prefix = nil
315
+ rescue CpfGen::TypeMismatchError => e
316
+ puts e.message
317
+ # CPF generator option "prefix" must be of type string. Got nil.
318
+ end
319
+
320
+ options.prefix = CpfGen::CpfGeneratorOptions::DEFAULT_PREFIX # explicit reset instead
321
+ ```
322
+
323
+ Check-digit computation failures from `cpf-dv` are handled internally by retrying generation with the same resolved options; they are not raised to callers under normal operation.
324
+
325
+ ### Other available resources
326
+
327
+ - **`CpfGen::CpfGeneratorOptions::CPF_LENGTH`**: `11`.
328
+ - **`CpfGen::CpfGeneratorOptions::CPF_PREFIX_MAX_LENGTH`**: `9`.
329
+ - **`CpfGen::CpfGeneratorOptions::DEFAULT_FORMAT`**, **`DEFAULT_PREFIX`**: Class-level default constants.
330
+
331
+ ## Contribution & Support
332
+
333
+ 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:
334
+
335
+ - ⭐ Starring the repository
336
+ - 🤝 Contributing to the codebase
337
+ - 💡 [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues)
338
+ - 🐛 [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
339
+
340
+ ## License
341
+
342
+ This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details.
343
+
344
+ ## Changelog
345
+
346
+ See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history.
347
+
348
+ ---
349
+
350
+ Made with ❤️ by [Lacus Solutions](https://github.com/LacusSolutions)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cpf_generator'
4
+
5
+ module CpfGen
6
+ module_function
7
+
8
+ # Helper function to simplify the usage of the {CpfGenerator} class.
9
+ #
10
+ # If no options are provided, it generates an 11-digit unformatted numeric CPF
11
+ # (e.g. +"47844241055"+) using default settings. If options are provided, they
12
+ # control +prefix+ and whether the result is formatted.
13
+ #
14
+ # Generates a valid 11-digit CPF (+prefix+, random numeric body, and computed
15
+ # check digits). With default options the result is unformatted numeric; pass
16
+ # +format: true+ for +000.000.000-00+ style output.
17
+ #
18
+ # @param options [CpfGeneratorOptions, Hash, nil] generator options
19
+ # @param keywords [Hash] option keyword overrides (mutually exclusive with +options+;
20
+ # see {CpfGeneratorOptions})
21
+ # @return [String] generated CPF
22
+ # @raise [InvalidArgumentCombinationError] if +options+ and a keyword argument are both given
23
+ # @raise [TypeMismatchError] if any option has an invalid type
24
+ # @raise [ValidationError] if +prefix+ is invalid
25
+ # @see CpfGenerator for detailed option descriptions
26
+ #
27
+ # @example
28
+ # CpfGen.cpf_gen # => "47844241055"
29
+ def cpf_gen(options = nil, **keywords)
30
+ CpfGenerator.new(options, **keywords).generate
31
+ end
32
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cpf-dv'
4
+ require 'lacus-utils'
5
+
6
+ require_relative 'cpf_generator_options'
7
+
8
+ module CpfGen
9
+ # Generator for CPF (Cadastro de Pessoa Física) identifiers. Builds valid
10
+ # 11-digit CPF values by combining an optional +prefix+ with a randomly
11
+ # generated sequence and computed check digits. Options control +prefix+ and
12
+ # whether the result is formatted (+000.000.000-00+).
13
+ class CpfGenerator
14
+ # Returns the default options used by this generator when per-call options
15
+ # are not provided.
16
+ #
17
+ # The returned object is the same instance used internally; mutating it (e.g.
18
+ # via setters on {CpfGeneratorOptions}) affects future {#generate} calls that
19
+ # do not pass +options+.
20
+ #
21
+ # @return [CpfGeneratorOptions] the instance default options
22
+ attr_reader :options
23
+
24
+ # Creates a new {CpfGenerator} with optional default options.
25
+ #
26
+ # Default options apply to every call to {#generate} unless overridden by the
27
+ # per-call +options+ argument or keyword overrides. Options control +prefix+
28
+ # and whether the generated CPF is formatted.
29
+ #
30
+ # +options+ and the keyword arguments are never merged with each other: when
31
+ # +options+ is given (a {CpfGeneratorOptions} instance or a {Hash}), it alone
32
+ # determines the default options; otherwise, the default options are built
33
+ # exclusively from the keyword arguments, with {CpfGeneratorOptions} filling
34
+ # in its own defaults for every keyword left as +nil+. Passing +options+
35
+ # together with any non-+nil+ keyword argument raises
36
+ # {InvalidArgumentCombinationError} instead of silently ignoring the keywords.
37
+ #
38
+ # When +options+ is a {CpfGeneratorOptions} instance, that instance is used
39
+ # directly (no copy is created). Mutating it later (e.g. via the {#options}
40
+ # reader or the original reference) affects future {#generate} calls that do
41
+ # not pass per-call options. When a plain {Hash} is passed instead, a new
42
+ # {CpfGeneratorOptions} instance is created from it.
43
+ #
44
+ # @param options [CpfGeneratorOptions, Hash, nil] default options
45
+ # @param keywords [Hash] option keyword overrides (mutually exclusive with +options+;
46
+ # see {CpfGeneratorOptions})
47
+ # @raise [InvalidArgumentCombinationError] if +options+ and a keyword argument are both given
48
+ # @raise [TypeMismatchError] if any option has an invalid type
49
+ # @raise [ValidationError] if +prefix+ is invalid
50
+ def initialize(options = nil, **keywords)
51
+ @options = resolve_default_options(options, keywords)
52
+ end
53
+
54
+ # Generates a valid CPF value.
55
+ #
56
+ # Builds an 11-digit CPF from the configured +prefix+ (if any), a random
57
+ # numeric sequence, and two computed check digits. If formatting is enabled,
58
+ # the result is returned as +000.000.000-00+.
59
+ #
60
+ # +options+ and the keyword arguments are never merged with each other: when
61
+ # +options+ is given (a {CpfGeneratorOptions} instance or a {Hash}), it alone
62
+ # overrides the instance default options for this call; otherwise, any
63
+ # non-+nil+ keyword argument overrides the instance default options for this
64
+ # call. When neither +options+ nor any keyword argument is given, the
65
+ # instance default options are used as-is. In every case, the instance
66
+ # default options themselves are left unchanged. Passing +options+ together
67
+ # with any non-+nil+ keyword argument raises {InvalidArgumentCombinationError}
68
+ # instead of silently ignoring the keywords.
69
+ #
70
+ # @param options [CpfGeneratorOptions, Hash, nil] per-call option overrides
71
+ # @param keywords [Hash] per-call option keyword overrides (mutually exclusive
72
+ # with +options+; see {CpfGeneratorOptions})
73
+ # @return [String] generated CPF
74
+ # @raise [InvalidArgumentCombinationError] if +options+ and a keyword argument are both given
75
+ # @raise [TypeMismatchError] if any option has an invalid type
76
+ # @raise [ValidationError] if +prefix+ is invalid
77
+ def generate(options = nil, **keywords)
78
+ actual_options = resolve_call_options(options, keywords)
79
+ generated_cpf = build_base_cpf(actual_options)
80
+
81
+ with_check_digits(generated_cpf, options, keywords) do |cpf_with_digits|
82
+ actual_options.format ? Utils.format_cpf(cpf_with_digits) : cpf_with_digits
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def resolve_default_options(options, keywords)
89
+ keyword_overrides = compact_keyword_overrides(keywords)
90
+ raise_ambiguous_options! if options && !keyword_overrides.empty?
91
+ return options if options.is_a?(CpfGeneratorOptions)
92
+ return CpfGeneratorOptions.new(options) if options
93
+
94
+ CpfGeneratorOptions.new(**keywords)
95
+ end
96
+
97
+ def resolve_call_options(options, keywords)
98
+ keyword_overrides = compact_keyword_overrides(keywords)
99
+ raise_ambiguous_options! if options && !keyword_overrides.empty?
100
+ return @options.copy.set(options) if options
101
+ return @options if keyword_overrides.empty?
102
+
103
+ @options.copy.set(keyword_overrides)
104
+ end
105
+
106
+ def compact_keyword_overrides(keywords)
107
+ CpfGeneratorOptions::OPTION_KEYS.each_with_object({}) do |key, overrides|
108
+ value = keywords[key]
109
+ overrides[key] = value unless value.nil?
110
+ end
111
+ end
112
+
113
+ def raise_ambiguous_options!
114
+ option_keywords = CpfGeneratorOptions::OPTION_KEYS.map { |key| "#{key}:" }.join(', ')
115
+
116
+ raise InvalidArgumentCombinationError,
117
+ "Pass either an options instance/Hash to `options`, or keyword arguments (#{option_keywords}), " \
118
+ 'not both.'
119
+ end
120
+
121
+ def build_base_cpf(actual_options)
122
+ digits_to_generate = CpfGeneratorOptions::CPF_PREFIX_MAX_LENGTH - actual_options.prefix.length
123
+
124
+ actual_options.prefix + LacusUtils.generate_random_sequence(
125
+ digits_to_generate,
126
+ :numeric
127
+ )
128
+ end
129
+
130
+ def with_check_digits(generated_cpf, options, keywords)
131
+ cpf_with_digits = CpfDV::CpfCheckDigits.new(generated_cpf).cpf
132
+
133
+ yield cpf_with_digits
134
+ rescue CpfDV::DomainError
135
+ generate(options, **keywords)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'types'
4
+
5
+ module CpfGen
6
+ # Layered option-resolution helpers for {CpfGeneratorOptions}.
7
+ module GeneratorOptionsResolution
8
+ private
9
+
10
+ def fold_layers(layers)
11
+ resolved = {}
12
+
13
+ layers.each do |layer|
14
+ source = layer_source(layer)
15
+ next if source.nil?
16
+
17
+ CpfGeneratorOptions::OPTION_KEYS.each do |key|
18
+ value = Utils.fetch_option(source, key)
19
+ resolved[key] = value unless value.nil?
20
+ end
21
+ end
22
+
23
+ resolved
24
+ end
25
+
26
+ def layer_source(layer)
27
+ return layer.all if layer.is_a?(CpfGeneratorOptions)
28
+ return layer if layer.is_a?(Hash)
29
+
30
+ nil
31
+ end
32
+
33
+ def apply_keyword_overrides(resolved, keywords)
34
+ CpfGeneratorOptions::OPTION_KEYS.each do |key|
35
+ value = keywords[key]
36
+ resolved[key] = value unless value.nil?
37
+ end
38
+ end
39
+
40
+ def assign_resolved_or_default(resolved)
41
+ CpfGeneratorOptions::OPTION_KEYS.each do |key|
42
+ value = resolved.key?(key) ? resolved[key] : CpfGeneratorOptions::DEFAULTS[key]
43
+ public_send("#{key}=", value)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Property accessors for {CpfGeneratorOptions}. Kept as a sibling module in this
49
+ # file (not a separate public API) so the options class stays under RuboCop's
50
+ # +Metrics/ClassLength+ budget.
51
+ module CpfGeneratorOptionProperties
52
+ # Returns a shallow copy of all current options.
53
+ #
54
+ # Exposes resolved +format+ and +prefix+ values. This is useful for creating
55
+ # snapshots of the current configuration.
56
+ #
57
+ # @return [Hash{Symbol => Object}] shallow copy of option values
58
+ def all
59
+ @options.dup
60
+ end
61
+
62
+ # Returns whether the generated CPF string will have the standard formatting
63
+ # (+000.000.000-00+).
64
+ #
65
+ # @return [Boolean]
66
+ def format
67
+ @options[:format]
68
+ end
69
+
70
+ # Sets whether the generated CPF string will have the standard formatting
71
+ # (+000.000.000-00+). The value is converted to a boolean, so truthy/falsy
72
+ # values are handled appropriately.
73
+ #
74
+ # +nil+ is not accepted: pass {CpfGeneratorOptions::DEFAULT_FORMAT} explicitly
75
+ # to reset this option to its default value.
76
+ #
77
+ # @param value [Boolean] enable formatting when truthy
78
+ # @raise [TypeMismatchError] if the value is +nil+
79
+ def format=(value)
80
+ raise TypeMismatchError.new(value, 'boolean', option_name: 'format') if value.nil?
81
+
82
+ @options[:format] = Utils.normalize_boolean(value)
83
+ end
84
+
85
+ # Returns the string used as the initial string of the generated CPF.
86
+ #
87
+ # Note: If the evaluated +prefix+ (after stripping non-digit characters) is
88
+ # longer than 9 digits, the extra digits are ignored, because a CPF has 9
89
+ # base digits followed by 2 calculated check digits.
90
+ #
91
+ # @return [String]
92
+ def prefix
93
+ @options[:prefix]
94
+ end
95
+
96
+ # Sets the string used as the initial string of the generated CPF. Only
97
+ # digits are kept and the rest is stripped. If provided, only the missing
98
+ # digits are generated randomly. For example, if the +prefix+ +"123456"+ (6
99
+ # digits) is given, only the next 3 digits are randomly generated and
100
+ # concatenated to the +prefix+.
101
+ #
102
+ # Note: If the evaluated +prefix+ (after stripping non-digit characters) is
103
+ # longer than 9 digits, the extra digits are ignored, because a CPF has 9
104
+ # base digits followed by 2 calculated check digits.
105
+ #
106
+ # +nil+ is not accepted: pass {CpfGeneratorOptions::DEFAULT_PREFIX} explicitly
107
+ # to reset this option to its default value.
108
+ #
109
+ # @param value [String] partial start string
110
+ # @raise [TypeMismatchError] if the value is not a +String+
111
+ # @raise [ValidationError] if +prefix+ is invalid
112
+ def prefix=(value)
113
+ actual_prefix = Utils.sanitize_prefix(value)
114
+
115
+ Utils.validate_prefix!(actual_prefix)
116
+
117
+ @options[:prefix] = actual_prefix
118
+ end
119
+ end
120
+
121
+ # Stores configuration for the CPF generator.
122
+ #
123
+ # Provides a centralized way to configure how CPF digits are generated,
124
+ # including partial start string (+prefix+) and formatting (+format+).
125
+ class CpfGeneratorOptions
126
+ include GeneratorOptionsResolution
127
+ include CpfGeneratorOptionProperties
128
+
129
+ # The standard length of a CPF (Cadastro de Pessoa Física) identifier (11
130
+ # digits).
131
+ CPF_LENGTH = 11
132
+
133
+ # Maximum length of the +prefix+ of a CPF.
134
+ CPF_PREFIX_MAX_LENGTH = CPF_LENGTH - 2
135
+
136
+ # Default value for the +format+ option. When +true+, the generated CPF
137
+ # string will have the standard formatting (+000.000.000-00+).
138
+ DEFAULT_FORMAT = false
139
+
140
+ # Default string used as the initial string of the generated CPF.
141
+ DEFAULT_PREFIX = ''
142
+
143
+ CPF_BASE_ID_LENGTH = 9
144
+ CPF_BASE_ID_LAST_INDEX = CPF_BASE_ID_LENGTH - 1
145
+ ZEROED_CPF_BASE_ID = '0' * CPF_BASE_ID_LENGTH
146
+
147
+ PREFIX_SANITIZE_PATTERN = /\D/
148
+
149
+ # Option keys managed by this class, in assignment order.
150
+ OPTION_KEYS = %i[format prefix].freeze
151
+
152
+ # Default value for each key in {OPTION_KEYS}, used to fill any option that
153
+ # is still unresolved once {#initialize} finishes merging its arguments.
154
+ DEFAULTS = {
155
+ format: DEFAULT_FORMAT,
156
+ prefix: DEFAULT_PREFIX
157
+ }.freeze
158
+
159
+ # Creates a new {CpfGeneratorOptions} instance.
160
+ #
161
+ # Options are resolved in three steps. Each step only overrides a key when it
162
+ # is given a non-+nil+ value; a +nil+ is always ignored in favor of whatever
163
+ # was resolved by a previous step.
164
+ #
165
+ # 1. Every positional +options+ layer (each either a {Hash} or another
166
+ # {CpfGeneratorOptions} instance) is folded left to right, so later
167
+ # layers take precedence over earlier ones.
168
+ # 2. The +format+ and +prefix+ keyword arguments are then applied on top of
169
+ # the folded layers. Keywords always have the highest precedence,
170
+ # overriding every positional layer.
171
+ # 3. Any option that is still unresolved after steps 1 and 2 is assigned its
172
+ # +DEFAULT_*+ value (see {DEFAULTS}).
173
+ #
174
+ # Because every option is fully resolved to a concrete, non-+nil+ value
175
+ # before assignment, the individual property setters (e.g. {#format=}) never
176
+ # receive +nil+ from this method — they always raise if given +nil+ directly.
177
+ #
178
+ # @param options [Array<CpfGeneratorOptions, Hash>] option layers merged in
179
+ # order (later layers win); a missing or +nil+ value for a key inside a
180
+ # layer is ignored and the previously resolved value is kept
181
+ # @param keywords [Hash] highest-precedence option overrides (see {OPTION_KEYS})
182
+ # @raise [TypeMismatchError] if any option has an invalid type
183
+ # @raise [ValidationError] if +prefix+ is invalid
184
+ def initialize(*options, **keywords)
185
+ @options = {}
186
+
187
+ resolved = fold_layers(options)
188
+ apply_keyword_overrides(resolved, keywords)
189
+ assign_resolved_or_default(resolved)
190
+ end
191
+
192
+ # Sets multiple options at once, following the same layered-override
193
+ # semantics as {#initialize} (positional layers folded left to right, then
194
+ # keyword arguments applied with the highest precedence; +nil+ is always
195
+ # ignored). Unlike {#initialize}, any option that is still unresolved after
196
+ # merging keeps its **current** value on this instance instead of falling
197
+ # back to its default — this method performs a partial update, not a
198
+ # re-initialization.
199
+ #
200
+ # @param options [Array<CpfGeneratorOptions, Hash>] option layers merged in
201
+ # order (later layers win)
202
+ # @param keywords [Hash] highest-precedence option overrides (see {OPTION_KEYS})
203
+ # @return [CpfGeneratorOptions] +self+
204
+ # @raise [TypeMismatchError] if any option has an invalid type
205
+ # @raise [ValidationError] if +prefix+ is invalid
206
+ def set(*options, **keywords)
207
+ resolved = fold_layers(options)
208
+ apply_keyword_overrides(resolved, keywords)
209
+ resolved.each { |key, value| public_send("#{key}=", value) }
210
+ self
211
+ end
212
+
213
+ # Returns a shallow copy of this options instance.
214
+ #
215
+ # @return [CpfGeneratorOptions] duplicated options for per-call merging
216
+ def copy
217
+ duplicate = self.class.allocate
218
+ duplicate.instance_variable_set(:@options, @options.dup)
219
+ duplicate
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lacus-utils'
4
+
5
+ module CpfGen
6
+ # Marker module mixed into every custom error raised by this library.
7
+ #
8
+ # Use +rescue CpfGen::Error+ to catch every library error regardless of native
9
+ # ancestry.
10
+ module Error; end
11
+
12
+ # API misuse error raised when an argument's runtime type does not match the
13
+ # type required by the API contract (a generator option).
14
+ class TypeMismatchError < TypeError
15
+ include Error
16
+
17
+ # @return [Object] the offending input value
18
+ attr_reader :actual_input
19
+
20
+ # @return [String] human-readable type of {#actual_input}
21
+ attr_reader :actual_type
22
+
23
+ # @return [String] description of the expected type
24
+ attr_reader :expected_type
25
+
26
+ # @return [String] the offending option key
27
+ attr_reader :option_name
28
+
29
+ # @param actual_input [Object] the offending option value
30
+ # @param expected_type [String] description of the expected type
31
+ # @param option_name [String] option key when the failure is option-related
32
+ def initialize(actual_input, expected_type, option_name:)
33
+ actual_type = LacusUtils.describe_type(actual_input)
34
+ super(
35
+ %(CPF generator option "#{option_name}" must be of type #{expected_type}. Got #{actual_type}.)
36
+ )
37
+ @actual_input = actual_input
38
+ @actual_type = actual_type
39
+ @expected_type = expected_type
40
+ @option_name = option_name
41
+ end
42
+ end
43
+
44
+ # API misuse error raised when the combination of provided arguments does not
45
+ # match any valid overload-style signature.
46
+ class InvalidArgumentCombinationError < ArgumentError
47
+ include Error
48
+ end
49
+
50
+ # Domain error ancestor for business-rule failures (validation and other domain
51
+ # leaves). Prefer raising a leaf subclass.
52
+ class DomainError < RangeError
53
+ include Error
54
+ end
55
+
56
+ # Domain error raised when a generator option has a valid type but violates a
57
+ # domain rule (invalid +prefix+). Structurally mirrors {CnpjGen::ValidationError}
58
+ # (including +expected_values+) even though CPF has no +type+ option that would
59
+ # populate that field.
60
+ class ValidationError < DomainError
61
+ # @return [String] the offending option name
62
+ attr_reader :option_name
63
+
64
+ # @return [Object] the offending option value
65
+ attr_reader :actual_input
66
+
67
+ # @return [String, nil] human-readable reason when the failure is prefix-related
68
+ attr_reader :reason
69
+
70
+ # @return [Array<String>, nil] reserved for structural parity with CNPJ; always
71
+ # +nil+ for CPF prefix validation
72
+ attr_reader :expected_values
73
+
74
+ # @param option_name [String] the offending option key
75
+ # @param actual_input [Object] the offending option value
76
+ # @param reason [String, nil] human-readable reason for a prefix failure
77
+ # @param expected_values [Array<String>, nil] unused for CPF (always +nil+)
78
+ def initialize(option_name, actual_input, reason: nil, expected_values: nil)
79
+ super(build_message(option_name, actual_input, reason, expected_values))
80
+ @option_name = option_name
81
+ @actual_input = actual_input
82
+ @reason = reason
83
+ @expected_values = expected_values&.dup&.freeze
84
+ end
85
+
86
+ private
87
+
88
+ def build_message(option_name, actual_input, reason, expected_values)
89
+ if expected_values
90
+ quoted = expected_values.map { |value| %("#{value}") }.join(', ')
91
+ "CPF generator option \"#{option_name}\" accepts only the following values: #{quoted}. " \
92
+ "Got \"#{actual_input}\"."
93
+ else
94
+ "CPF generator option \"#{option_name}\" with value \"#{actual_input}\" is invalid. #{reason}"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CpfGen
4
+ # Options input accepted by constructors and merge helpers.
5
+ #
6
+ # May be a {CpfGeneratorOptions} instance, a {Hash} of option keys, or +nil+.
7
+ #
8
+ # Resolved options contain:
9
+ #
10
+ # - +format+ [Boolean] — whether to format the generated CPF string as
11
+ # +000.000.000-00+ (default: +false+).
12
+ # - +prefix+ [String] — a partial string containing 0 to 9 digits to use as the
13
+ # start of the generated CPF. Only digits are kept; the rest is stripped. If
14
+ # provided, only the missing digits are generated randomly. For example, if
15
+ # the +prefix+ +"123456"+ (6 digits) is given, only the next 3 digits are
16
+ # randomly generated and concatenated to the +prefix+.
17
+ #
18
+ # Note: If the evaluated +prefix+ (after stripping non-digit characters) is
19
+ # longer than 9 digits, the extra digits are ignored, because a CPF has 9
20
+ # base digits followed by 2 calculated check digits.
21
+ #
22
+ # @see CpfGeneratorOptions
23
+ CpfGeneratorOptionsInput = Object
24
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CpfGen
4
+ # Low-level helpers used by {CpfGenerator} and {CpfGeneratorOptions}.
5
+ #
6
+ # @api private
7
+ module Utils
8
+ module_function
9
+
10
+ # rubocop:disable Naming/PredicateMethod -- coercion helper, not a predicate query
11
+ def normalize_boolean(value)
12
+ return false if [false, '', 0].include?(value)
13
+
14
+ !!value
15
+ end
16
+ # rubocop:enable Naming/PredicateMethod
17
+
18
+ def assert_string_option!(option_name, value)
19
+ return if value.is_a?(String)
20
+
21
+ raise TypeMismatchError.new(value, 'string', option_name: option_name)
22
+ end
23
+
24
+ def fetch_option(source, key)
25
+ return source[key] if source.key?(key)
26
+ return source[key.to_s] if source.key?(key.to_s)
27
+
28
+ nil
29
+ end
30
+
31
+ def sanitize_prefix(value)
32
+ assert_string_option!('prefix', value)
33
+
34
+ sanitized = value.gsub(CpfGeneratorOptions::PREFIX_SANITIZE_PATTERN, '')
35
+ sanitized[0, CpfGeneratorOptions::CPF_PREFIX_MAX_LENGTH]
36
+ end
37
+
38
+ def validate_prefix!(partial_cpf)
39
+ validate_prefix_base_id!(partial_cpf)
40
+ validate_prefix_non_repeated_digits!(partial_cpf)
41
+ end
42
+
43
+ def validate_prefix_base_id!(partial_cpf)
44
+ return if partial_cpf.length < CpfGeneratorOptions::CPF_BASE_ID_LENGTH
45
+
46
+ cpf_base_id = partial_cpf[0, CpfGeneratorOptions::CPF_BASE_ID_LAST_INDEX + 1]
47
+
48
+ return unless cpf_base_id == CpfGeneratorOptions::ZEROED_CPF_BASE_ID
49
+
50
+ raise ValidationError.new(
51
+ 'prefix',
52
+ partial_cpf,
53
+ reason: 'Zeroed base ID is not eligible.'
54
+ )
55
+ end
56
+
57
+ def validate_prefix_non_repeated_digits!(partial_cpf)
58
+ return if partial_cpf.length < CpfGeneratorOptions::CPF_PREFIX_MAX_LENGTH
59
+
60
+ first_character = partial_cpf[0]
61
+
62
+ return unless partial_cpf == first_character * CpfGeneratorOptions::CPF_PREFIX_MAX_LENGTH
63
+
64
+ raise ValidationError.new(
65
+ 'prefix',
66
+ partial_cpf,
67
+ reason: 'Repeated digits are not considered valid.'
68
+ )
69
+ end
70
+
71
+ # Formats a raw 11-digit CPF into the standard masked representation.
72
+ def format_cpf(raw)
73
+ "#{raw[0, 3]}.#{raw[3, 3]}.#{raw[6, 3]}-#{raw[9, 2]}"
74
+ end
75
+ end
76
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CpfGen
4
- VERSION = '0.0.0'
4
+ VERSION = '1.0.0'
5
5
  end
data/src/cpf-gen.rb CHANGED
@@ -1,10 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cpf-dv'
4
3
  require_relative 'cpf-gen/version'
4
+ require_relative 'cpf-gen/errors'
5
+ require_relative 'cpf-gen/types'
6
+ require_relative 'cpf-gen/cpf_generator_options'
7
+ require_relative 'cpf-gen/utils'
8
+ require_relative 'cpf-gen/cpf_generator'
9
+ require_relative 'cpf-gen/cpf_gen'
5
10
 
11
+ # Generates valid CPF (Cadastro de Pessoa Física) identifiers.
12
+ #
13
+ # Errors fall into two categories:
14
+ #
15
+ # - *API misuse* — the caller supplied a wrong type or an incompatible argument
16
+ # combination. Raised as {CpfGen::TypeMismatchError} or
17
+ # {CpfGen::InvalidArgumentCombinationError}.
18
+ # - *Domain errors* — the call shape was valid, but a value violates a business
19
+ # rule. Invalid +prefix+ values raise {CpfGen::ValidationError} under
20
+ # {CpfGen::DomainError}.
21
+ #
22
+ # Every custom error includes the {CpfGen::Error} marker module so consumers can
23
+ # +rescue CpfGen::Error+ for a library-wide catch.
24
+ #
25
+ # Public API:
26
+ #
27
+ # - {CpfGen.cpf_gen}
28
+ # - {CpfGen::CpfGenerator}, {CpfGen::CpfGeneratorOptions}
29
+ # - {CpfGen::CPF_LENGTH}, {CpfGen::CPF_PREFIX_MAX_LENGTH}
30
+ # - Error marker {CpfGen::Error}; domain ancestor {CpfGen::DomainError};
31
+ # misuse errors {CpfGen::TypeMismatchError} and
32
+ # {CpfGen::InvalidArgumentCombinationError}; domain leaf
33
+ # {CpfGen::ValidationError}
34
+ #
35
+ # @example
36
+ # require 'cpf-gen'
37
+ #
38
+ # CpfGen.cpf_gen # => e.g. "47844241055"
6
39
  module CpfGen
7
- def self.hello
8
- 'cpf-gen'
9
- end
40
+ # The standard length of a CPF (Cadastro de Pessoa Física) identifier (11
41
+ # digits).
42
+ CPF_LENGTH = CpfGeneratorOptions::CPF_LENGTH
43
+
44
+ # Maximum length of the +prefix+ of a CPF.
45
+ CPF_PREFIX_MAX_LENGTH = CpfGeneratorOptions::CPF_PREFIX_MAX_LENGTH
10
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cpf-gen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julio L. Muller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-04 00:00:00.000000000 Z
11
+ date: 2026-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cpf-dv
@@ -16,26 +16,62 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 1.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '0'
27
- description:
29
+ version: 1.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: lacus-utils
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.1.0
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: 2.0.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.1.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 2.0.0
53
+ description: Utility to generate CPF (Brazilian Individual's Taxpayer ID)
28
54
  email:
55
+ - juliolmuller@outlook.com
29
56
  executables: []
30
57
  extensions: []
31
58
  extra_rdoc_files: []
32
59
  files:
60
+ - LICENSE
61
+ - README.md
33
62
  - src/cpf-gen.rb
63
+ - src/cpf-gen/cpf_gen.rb
64
+ - src/cpf-gen/cpf_generator.rb
65
+ - src/cpf-gen/cpf_generator_options.rb
66
+ - src/cpf-gen/errors.rb
67
+ - src/cpf-gen/types.rb
68
+ - src/cpf-gen/utils.rb
34
69
  - src/cpf-gen/version.rb
35
70
  homepage: https://github.com/LacusSolutions/br-utils-ruby
36
71
  licenses:
37
72
  - MIT
38
73
  metadata:
74
+ source_code_uri: https://github.com/LacusSolutions/br-utils-ruby
39
75
  rubygems_mfa_required: 'true'
40
76
  post_install_message:
41
77
  rdoc_options: []
@@ -45,7 +81,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
81
  requirements:
46
82
  - - ">="
47
83
  - !ruby/object:Gem::Version
48
- version: '3.2'
84
+ version: '3.1'
49
85
  required_rubygems_version: !ruby/object:Gem::Requirement
50
86
  requirements:
51
87
  - - ">="
@@ -55,5 +91,5 @@ requirements: []
55
91
  rubygems_version: 3.4.19
56
92
  signing_key:
57
93
  specification_version: 4
58
- summary: Generate random valid CPF numbers (Brazilian personal ID)
94
+ summary: Generate CPF (Brazilian Individual's Taxpayer ID)
59
95
  test_files: []