From 12b78d8483278c1f41b138a2c6527950034519ba Mon Sep 17 00:00:00 2001 From: "Lucas F." Date: Thu, 16 Jul 2026 11:30:46 -0300 Subject: [PATCH] initial --- .gitignore | 41 +++++++++++++++++ Makefile | 10 ++++ README.md | 24 ++++++++++ cnpj/generate.go | 88 ++++++++++++++++++++++++++++++++++++ cnpj/generate_test.go | 103 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ go.sum | 0 main.go | 27 +++++++++++ 8 files changed, 296 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 cnpj/generate.go create mode 100644 cnpj/generate_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4f39af --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..feb1447 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6d5a1e --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/cnpj/generate.go b/cnpj/generate.go new file mode 100644 index 0000000..ca3b65a --- /dev/null +++ b/cnpj/generate.go @@ -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:]) +} diff --git a/cnpj/generate_test.go b/cnpj/generate_test.go new file mode 100644 index 0000000..f7cfa20 --- /dev/null +++ b/cnpj/generate_test.go @@ -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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c86e546 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.lucasf.xyz/public/cnpjgen + +go 1.26.4 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go new file mode 100644 index 0000000..ea9bad2 --- /dev/null +++ b/main.go @@ -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)) + } +}