commit d161ab35a77983d7599d9cdb0d43cefa3b1fc372 Author: Lucas F. Date: Thu Jul 16 11:08:24 2026 -0300 initial 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..e8f66c6 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +.PHONY: build test clean + +build: + @mkdir -p bin && go build -o bin/cpfgen . + +test: + @go test -v ./... + +clean: + @rm -rf bin diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c52b04b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.lucasf.xyz/public/cpfgen + +go 1.26.4 diff --git a/main.go b/main.go new file mode 100644 index 0000000..e8410af --- /dev/null +++ b/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "fmt" + "math/rand/v2" + "strings" +) + +func main() { + onlyNum := flag.Bool("num", false, "generate only numbers (no formatting)") + flag.Parse() + + cpf := generateCPF() + + if *onlyNum { + fmt.Println(cpf) + } else { + fmt.Printf("%s.%s.%s-%s\n", cpf[:3], cpf[3:6], cpf[6:9], cpf[9:]) + } +} + +func generateCPF() string { + digits := make([]int, 11) + + for i := range 9 { + digits[i] = rand.IntN(10) + } + + sum := 0 + for i := range 9 { + sum += digits[i] * (10 - i) + } + rem := sum % 11 + if rem < 2 { + digits[9] = 0 + } else { + digits[9] = 11 - rem + } + + sum = 0 + for i := range 10 { + sum += digits[i] * (11 - i) + } + rem = sum % 11 + if rem < 2 { + digits[10] = 0 + } else { + digits[10] = 11 - rem + } + + var result strings.Builder + for _, d := range digits { + fmt.Fprintf(&result, "%d", d) + } + return result.String() +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..2513a13 --- /dev/null +++ b/main_test.go @@ -0,0 +1,87 @@ +package main + +import ( + "strconv" + "testing" +) + +func TestGenerateCPFLength(t *testing.T) { + cpf := generateCPF() + if len(cpf) != 11 { + t.Fatalf("expected length 11, got %d: %s", len(cpf), cpf) + } +} + +func TestGenerateCPFCheckDigits(t *testing.T) { + for range 100 { + cpf := generateCPF() + if !isValidCPF(cpf) { + t.Fatalf("invalid CPF generated: %s", cpf) + } + } +} + +func TestGenerateCPFAllDigits(t *testing.T) { + seen := make(map[string]bool) + for range 1000 { + cpf := generateCPF() + if seen[cpf] { + t.Fatal("duplicate CPF generated") + } + seen[cpf] = true + } +} + +func isValidCPF(cpf string) bool { + if len(cpf) != 11 { + return false + } + + digits := make([]int, 11) + for i, ch := range cpf { + d, err := strconv.Atoi(string(ch)) + if err != nil { + return false + } + digits[i] = d + } + + allEqual := true + for i := 1; i < 11; i++ { + if digits[i] != digits[0] { + allEqual = false + break + } + } + if allEqual { + return false + } + + sum := 0 + for i := range 9 { + sum += digits[i] * (10 - i) + } + rem := sum % 11 + var d1 int + if rem < 2 { + d1 = 0 + } else { + d1 = 11 - rem + } + if d1 != digits[9] { + return false + } + + sum = 0 + for i := range 10 { + sum += digits[i] * (11 - i) + } + rem = sum % 11 + var d2 int + if rem < 2 { + d2 = 0 + } else { + d2 = 11 - rem + } + return d2 == digits[10] +}