Skip to content

Commit 9935ea0

Browse files
theskyinflamesJaume
andauthored
fix: refactor CLIs to main()->run() pattern and fix exit codes (#17)
- Replace os.Exit(-1) with os.Exit(1) for standard error exit codes - Refactor main() -> run() int pattern so deferred cleanup fires before exit - Remove exitIfError helpers; handle errors inline with proper defer semantics - Update AGENTS.md to mark gotcha as resolved Co-authored-by: Jaume <jaume@example.com>
1 parent 5be34d6 commit 9935ea0

3 files changed

Lines changed: 59 additions & 53 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- ~~**No real-crypto e2e test:** `lib/fixtures_test.go` uses generated mocks (`EncrypterMock`/`DecrypterMock`), so encryption/decryption round-trips are never tested with real AES-256. Don't assume integration coverage exists.~~ ✅ Fixed — `TestRealCryptoEncodingDecodingRoundTrip` in `lib/e2e_test.go` exercises full encode→decode pipeline with real AES-256.
77
- **Formatters are enforced:** `gofumpt` + `goimports` run as linters. Run `golangci-lint run` locally (or `make lint`) before pushing — it also verifies `go mod tidy` didn't change anything (`git diff --quiet go.mod go.sum`).
88
- ~~**WASM build typo:** `make build-wasm` outputs `assets/world2png.wasm` (missing 'd'). Preserve filename for backward compatibility with `word2pngUI`.~~ ✅ Fixed — now outputs `word2png.wasm` with a copy as `world2png.wasm` for backward compat.
9-
- **`os.Exit()` + non-standard code:** Both CLIs (`cmd/word2png/`, `cmd/png2word/`) call `os.Exit(-1)` on failure, skipping deferred cleanup. Handle with care.
9+
- ~~**`os.Exit()` + non-standard code:** Both CLIs (`cmd/word2png/`, `cmd/png2word/`) call `os.Exit(-1)` on failure, skipping deferred cleanup. Handle with care.~~ ✅ Fixed — refactored to `main() -> run() int` pattern so defers fire before exit; replaced `os.Exit(-1)` with standard `os.Exit(1)`.
1010
- **`cmd/wasm/` excluded from golangci-lint** (see `.golangci.yml` `build-tags: [infra]` and WASM build constraint).
1111

1212
## Commands Quick Reference

cmd/png2word/main.go

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@ import (
1515
const defaultFilter = ".*"
1616

1717
func main() {
18-
var (
19-
file = kingpin.Flag("file", "Coded image to be used as words code if it's filled").Short('f').String()
20-
b64 = kingpin.Flag("b64", "b64 string with the coded image").String()
21-
debug = kingpin.Flag("debug", "writes a debug file").Short('d').Bool()
22-
filter = kingpin.Flag("filter", "only shows the words that match the regex expression").String()
18+
os.Exit(run())
19+
}
2320

24-
// dangerous zone
21+
func run() int {
22+
var (
23+
file = kingpin.Flag("file", "Coded image to be used as words code if it's filled").Short('f').String()
24+
b64 = kingpin.Flag("b64", "b64 string with the coded image").String()
25+
debug = kingpin.Flag("debug", "writes a debug file").Short('d').Bool()
26+
filter = kingpin.Flag("filter", "only shows the words that match the regex expression").String()
2527
showSeed = kingpin.Flag("show-seed", "shows the entered seed").Short('s').Bool()
26-
27-
debugFile *os.File
28-
err error
2928
)
3029
kingpin.Parse()
3130

@@ -38,20 +37,28 @@ func main() {
3837
*filter = defaultFilter
3938
}
4039
matchFilter, err := regexp.Compile(*filter)
41-
exitIfError(err)
40+
if err != nil {
41+
fmt.Printf("ERROR: %s\n", err.Error())
42+
return 1
43+
}
4244

45+
var debugFile *os.File
4346
if debug != nil && *debug {
4447
debugFile, err = os.Create("./decrypted-bytes.txt")
45-
exitIfError(err)
46-
defer func() {
47-
debugFile.Close()
48-
}()
48+
if err != nil {
49+
fmt.Printf("ERROR: %s\n", err.Error())
50+
return 1
51+
}
52+
defer debugFile.Close()
4953
}
5054

5155
decrypter := lib.NewAES256(seed)
5256
decoder := lib.NewDecoder(lib.Rune2Color(seed), decrypter, lib.DecodeDebugWriterOpt(debugFile))
5357
words, err := decoder.DecodeFromSource(*file, *b64)
54-
exitIfError(err)
58+
if err != nil {
59+
fmt.Printf("ERROR: %s\n", err.Error())
60+
return 1
61+
}
5562

5663
fmt.Println("decoding process finished.")
5764
fmt.Printf("Have been decoded %d words:\n\n", len(words))
@@ -69,15 +76,8 @@ func main() {
6976

7077
err = pterm.DefaultTable.WithBoxed(true).WithHasHeader().WithRowSeparator("-").WithHeaderRowSeparator("-").WithLeftAlignment().WithData(values).Render()
7178
if err != nil {
72-
fmt.Printf("Something went wrong: %s", err.Error())
73-
os.Exit(-1)
74-
}
75-
os.Exit(0)
76-
}
77-
78-
func exitIfError(err error) {
79-
if err != nil {
80-
fmt.Printf("ERROR: %s\n", err.Error())
81-
os.Exit(-1)
79+
fmt.Printf("Something went wrong: %s\n", err.Error())
80+
return 1
8281
}
82+
return 0
8383
}

cmd/word2png/main.go

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,17 @@ import (
1212
)
1313

1414
func main() {
15+
os.Exit(run())
16+
}
17+
18+
func run() int {
1519
var (
1620
imagePath = kingpin.Flag("file", "Save to the especified file if it's filled").Short('f').String()
1721
words = kingpin.Flag("words", "list of words to encode").Short('w').Strings()
1822
debug = kingpin.Flag("debug", "writes a debug file").Short('d').Bool()
1923
b64 = kingpin.Flag("b64", "b64encoded image").String()
2024
removeWords = kingpin.Flag("remove-word", "remove a word from an image by index number").Short('r').Ints()
21-
22-
// dangerous zone
23-
showSeed = kingpin.Flag("show-seed", "shows the entered seed").Short('s').Bool()
24-
25-
debugFile *os.File
26-
err error
25+
showSeed = kingpin.Flag("show-seed", "shows the entered seed").Short('s').Bool()
2726
)
2827
kingpin.Parse()
2928

@@ -32,54 +31,61 @@ func main() {
3231
pterm.DefaultBasicText.Printf("Entered seed: %s\n", pterm.BgYellow.Sprint(pterm.Black(seed)))
3332
}
3433

34+
var debugFile *os.File
3535
if debug != nil && *debug {
36+
var err error
3637
debugFile, err = os.Create("./encrypted-bytes.txt")
37-
exitIfError(err)
38-
defer func() {
39-
debugFile.Close()
40-
}()
38+
if err != nil {
39+
fmt.Printf("ERROR: %s\n", err.Error())
40+
return 1
41+
}
42+
defer debugFile.Close()
4143
}
4244

4345
aes256 := lib.NewAES256(seed)
4446

45-
// If the image already exists, received words will be appended to the existent ones
4647
if (*imagePath != "" && imageExists(*imagePath)) || *b64 != "" {
4748
decoder := lib.NewDecoder(lib.Rune2Color(seed), aes256, lib.DecodeDebugWriterOpt(debugFile))
4849
beforeWords, err := decoder.DecodeFromSource(*imagePath, *b64)
49-
exitIfError(err)
50+
if err != nil {
51+
fmt.Printf("ERROR: %s\n", err.Error())
52+
return 1
53+
}
5054
*words = append(beforeWords, *words...)
5155
}
5256

53-
// If remove-words flag has been provided, it's applied
5457
*words = RemoveWordsByIdx(*words, *removeWords)
5558

5659
encoder := lib.NewEncoder(lib.Rune2Color(seed), aes256, lib.EncoderDebugWriterOpt(debugFile))
5760
b, err := encoder.Encode(*words)
58-
exitIfError(err)
61+
if err != nil {
62+
fmt.Printf("ERROR: %s\n", err.Error())
63+
return 1
64+
}
5965

6066
switch {
6167
case *imagePath != "":
62-
exitIfError(lib.SaveEncodedImage(b, *imagePath))
68+
if err := lib.SaveEncodedImage(b, *imagePath); err != nil {
69+
fmt.Printf("ERROR: %s\n", err.Error())
70+
return 1
71+
}
6372
default:
6473
b64Encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout)
65-
_, err = b64Encoder.Write(b)
66-
exitIfError(err)
67-
exitIfError(b64Encoder.Close())
74+
if _, err = b64Encoder.Write(b); err != nil {
75+
fmt.Printf("ERROR: %s\n", err.Error())
76+
return 1
77+
}
78+
if err := b64Encoder.Close(); err != nil {
79+
fmt.Printf("ERROR: %s\n", err.Error())
80+
return 1
81+
}
6882
}
6983

7084
fmt.Println("\ncoding process finished")
71-
os.Exit(0)
72-
}
73-
74-
func exitIfError(err error) {
75-
if err != nil {
76-
fmt.Printf("ERROR: %s\n", err.Error())
77-
os.Exit(-1)
78-
}
85+
return 0
7986
}
8087

8188
func imageExists(imagePath string) bool {
82-
// if error is nil, I guess the file exists
8389
if _, err := os.Stat(imagePath); err == nil {
8490
return true
8591
}

0 commit comments

Comments
 (0)