initial
This commit is contained in:
commit
12b78d8483
8 changed files with 296 additions and 0 deletions
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal 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
10
Makefile
Normal 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
24
README.md
Normal 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
88
cnpj/generate.go
Normal 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
103
cnpj/generate_test.go
Normal 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
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module git.lucasf.xyz/public/cnpjgen
|
||||||
|
|
||||||
|
go 1.26.4
|
||||||
0
go.sum
Normal file
0
go.sum
Normal file
27
main.go
Normal file
27
main.go
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue