This commit is contained in:
Lucas F. 2026-07-16 11:30:46 -03:00
commit 12b78d8483
8 changed files with 296 additions and 0 deletions

41
.gitignore vendored Normal file
View file

@ -0,0 +1,41 @@
## Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
bin
# Test binary, built with `go test -c`
*.test
# Code coverage profiles and other test artifacts
*.out
coverage.*
*.coverprofile
profile.cov
# Dependency directories (remove the comment below to include it)
# vendor/
# development notes
/devnotes
# Go workspace file
go.work
go.work.sum
# env file
.env
# compiled binary at root
/main
# Editor/IDE
# .idea/
# .vscode/

10
Makefile Normal file
View file

@ -0,0 +1,10 @@
.PHONY: build test clean
build:
@mkdir -p bin && go build -o bin/cnpjgen .
test:
@go test -v ./...
clean:
@rm -rf bin

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# cnpjgen
CLI para gerar CNPJs válidos em Go.
## Instalação
```sh
make build
```
## Rodar os testes
```sh
make test
```
## Uso
```sh
./bin/cnpjgen # CNPJ numérico formatado (XX.XXX.XXX/XXXX-XX)
./bin/cnpjgen -num # apenas os números/dígitos
./bin/cnpjgen -alpha # CNPJ alfanumérico (formato novo)
./bin/cnpjgen -alpha -num # CNPJ alfanumérico sem formatação
```

88
cnpj/generate.go Normal file
View file

@ -0,0 +1,88 @@
package cnpj
import (
"fmt"
"math/rand"
"strings"
)
var (
cnpjP1 = []int{5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
cnpjP2 = []int{6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2}
)
const alphaChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
func Generate() string {
return generate(false)
}
func GenerateAlpha() string {
return generate(true)
}
func generate(alpha bool) string {
for {
var b strings.Builder
b.Grow(14)
for range 12 {
if alpha {
b.WriteByte(alphaChars[rand.Intn(len(alphaChars))])
} else {
b.WriteByte(byte('0' + rand.Intn(10)))
}
}
s := b.String()
if isRepetitive(s) {
continue
}
dv1 := computeDV(s, cnpjP1, alpha)
s2 := s + string(byte('0'+dv1))
dv2 := computeDV(s2, cnpjP2, alpha)
return s + string(byte('0'+dv1)) + string(byte('0'+dv2))
}
}
func computeDV(s string, table []int, alpha bool) int {
total := 0
for i, w := range table {
c := s[i]
var val int
if alpha {
val = alphaValue(rune(c))
} else {
val = int(c - '0')
}
total += val * w
}
r := total % 11
if r >= 2 {
return 11 - r
}
return 0
}
func alphaValue(r rune) int {
return int(r) - 48
}
func isRepetitive(s string) bool {
if len(s) == 0 {
return false
}
c := s[0]
for i := 1; i < len(s); i++ {
if s[i] != c {
return false
}
}
return true
}
func Format(cnpj string) string {
if len(cnpj) != 14 {
return cnpj
}
return fmt.Sprintf("%s.%s.%s/%s-%s",
cnpj[:2], cnpj[2:5], cnpj[5:8], cnpj[8:12], cnpj[12:])
}

103
cnpj/generate_test.go Normal file
View file

@ -0,0 +1,103 @@
package cnpj
import (
"testing"
)
func TestGenerate(t *testing.T) {
for range 100 {
cnpj := Generate()
if len(cnpj) != 14 {
t.Fatalf("expected 14 digits, got %d: %s", len(cnpj), cnpj)
}
for _, c := range cnpj {
if c < '0' || c > '9' {
t.Fatalf("non-digit character %c in %s", c, cnpj)
}
}
if isRepetitive(cnpj) {
t.Fatalf("repetitive CNPJ generated: %s", cnpj)
}
if !validateDV(cnpj, false) {
t.Fatalf("invalid check digits for %s", cnpj)
}
}
}
func TestGenerateAlpha(t *testing.T) {
for range 100 {
cnpj := GenerateAlpha()
if len(cnpj) != 14 {
t.Fatalf("expected 14 chars, got %d: %s", len(cnpj), cnpj)
}
for _, c := range cnpj[:12] {
if !isAlphaNumeric(byte(c)) {
t.Fatalf("non-alphanumeric character %c in base of %s", c, cnpj)
}
}
for _, c := range cnpj[12:] {
if c < '0' || c > '9' {
t.Fatalf("DV must be digit, got %c in %s", c, cnpj)
}
}
if isRepetitive(cnpj) {
t.Fatalf("repetitive CNPJ generated: %s", cnpj)
}
if !validateDV(cnpj, true) {
t.Fatalf("invalid check digits for %s", cnpj)
}
}
}
func TestUniqueness(t *testing.T) {
seen := make(map[string]bool)
for range 50 {
cnpj := Generate()
if seen[cnpj] {
t.Fatalf("duplicate CNPJ generated: %s", cnpj)
}
seen[cnpj] = true
}
for range 50 {
cnpj := GenerateAlpha()
if seen[cnpj] {
t.Fatalf("duplicate CNPJ generated: %s", cnpj)
}
seen[cnpj] = true
}
}
func TestFormat(t *testing.T) {
tests := []struct {
input string
want string
}{
{"12345678000195", "12.345.678/0001-95"},
{"11111111111111", "11.111.111/1111-11"},
{"", ""},
{"123", "123"},
{"ABCDEFGHIJKLMN", "AB.CDE.FGH/IJKL-MN"},
}
for _, tt := range tests {
got := Format(tt.input)
if got != tt.want {
t.Errorf("Format(%q) = %q; want %q", tt.input, got, tt.want)
}
}
}
func isAlphaNumeric(c byte) bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
}
func validateDV(s string, alpha bool) bool {
if len(s) != 14 {
return false
}
if isRepetitive(s) {
return false
}
dv1 := computeDV(s[:12], cnpjP1, alpha)
dv2 := computeDV(s[:12]+string(byte('0'+dv1)), cnpjP2, alpha)
return int(s[12]-'0') == dv1 && int(s[13]-'0') == dv2
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module git.lucasf.xyz/public/cnpjgen
go 1.26.4

0
go.sum Normal file
View file

27
main.go Normal file
View file

@ -0,0 +1,27 @@
package main
import (
"flag"
"fmt"
"git.lucasf.xyz/public/cnpjgen/cnpj"
)
func main() {
numFlag := flag.Bool("num", false, "exibe apenas os números")
alphaFlag := flag.Bool("alpha", false, "gera CNPJ alfanumérico")
flag.Parse()
var result string
if *alphaFlag {
result = cnpj.GenerateAlpha()
} else {
result = cnpj.Generate()
}
if *numFlag {
fmt.Println(result)
} else {
fmt.Println(cnpj.Format(result))
}
}