Skip to content

Commit 5d45e9f

Browse files
committed
feat: refactor assembler generator entrypoint for robustness and maintainability
- split monolithic main logic into run, parseArgs, and helpers - add loadOnePackage to DRY package loading and error checks - improve error propagation; centralize exit in main - add safeDir and enhance dirFor handling of “.” / “./…” - keep behavior compatible while improving structure and readability
1 parent 1507c34 commit 5d45e9f

1 file changed

Lines changed: 112 additions & 61 deletions

File tree

cmd/assemble/main.go

Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,51 +15,75 @@ import (
1515
"github.com/xfrr/assemble/internal/render"
1616
)
1717

18+
/* =========================
19+
CLI
20+
========================= */
21+
22+
type cliArgs struct {
23+
pkgArg string
24+
varName string
25+
funcName string
26+
outputPath string
27+
}
28+
1829
func main() {
19-
var (
20-
pkgArg = flag.String("pkg", ".", "Go package (import path or ./relative)")
21-
varName = flag.String("var", "Core", "assemble.Module variable name to parse (ignored if -fn is provided)")
22-
funcName = flag.String("fn", "", "function name that returns assemble.Assemble(...) (optional)")
23-
outputPath = flag.String("o", "gen.go", "output file (e.g., ./build_assemble_gen.go)")
24-
)
30+
log.SetFlags(0) // clean logs
31+
32+
args := parseArgs()
33+
if err := run(args); err != nil {
34+
log.Fatal(err)
35+
}
36+
}
37+
38+
func parseArgs() cliArgs {
39+
var a cliArgs
40+
flag.StringVar(&a.pkgArg, "pkg", ".", "Go package (import path or ./relative)")
41+
flag.StringVar(&a.varName, "var", "Core", "assemble.Module variable name to parse (ignored if -fn is provided)")
42+
flag.StringVar(&a.funcName, "fn", "", "function name that returns assemble.Assemble(...) (optional)")
43+
flag.StringVar(&a.outputPath, "o", "gen.go", "output file (e.g., ./build_assemble_gen.go)")
2544
flag.Parse()
26-
if *outputPath == "" {
45+
46+
if a.outputPath == "" {
2747
log.Fatal("-o output file is required")
2848
}
49+
return a
50+
}
2951

52+
func run(a cliArgs) error {
3053
fset := token.NewFileSet()
3154

3255
switch {
33-
case *funcName != "":
34-
mod, pkgPath, genFn, err := extractFromFunctionTwoPass(*pkgArg, *funcName, fset)
56+
case a.funcName != "":
57+
// Two-pass: (A) loose AST to find the module reference, (B) typed load to parse it.
58+
mod, pkgPath, genFn, err := extractFromFunctionTwoPass(a.pkgArg, a.funcName, fset)
3559
if err != nil {
36-
log.Fatalf("parse function: %v", err)
60+
return fmt.Errorf("parse function: %w", err)
3761
}
38-
writeOut(*outputPath, *varName, genFn, pkgPath, mod)
62+
return writeOut(a.outputPath, a.varName, genFn, pkgPath, mod)
3963

4064
default:
41-
// -------- Variable-export mode (single-pass) ----------
42-
cfg := loadConfig(dirFor(*pkgArg), nil, fset)
43-
pkgs, loadErr := packages.Load(cfg, *pkgArg)
44-
if loadErr != nil {
45-
log.Fatalf("load: %v", loadErr)
46-
}
47-
if packages.PrintErrors(pkgs) > 0 || len(pkgs) == 0 {
48-
log.Fatalf("failed loading package %q", *pkgArg)
65+
// Single-pass variable mode.
66+
cfg := loadConfig(dirFor(a.pkgArg), nil, fset)
67+
p, err := loadOnePackage(cfg, a.pkgArg, "load package")
68+
if err != nil {
69+
return err
4970
}
50-
p := pkgs[0]
5171

52-
mod, extractErr := parser.ExtractModule(p, *varName, fset)
53-
if extractErr != nil {
54-
log.Fatalf("parse module: %v", extractErr)
72+
mod, err := parser.ExtractModule(p, a.varName, fset)
73+
if err != nil {
74+
return fmt.Errorf("parse module: %w", err)
5575
}
56-
5776
mod.SetPkgName(p.Name)
58-
writeOut(*outputPath, *varName, "Assemble"+*varName, p.PkgPath, mod)
77+
78+
return writeOut(a.outputPath, a.varName, "Assemble"+a.varName, p.PkgPath, mod)
5979
}
6080
}
6181

62-
func writeOut(outputPath, varName, genFnName, currentPkgPath string, mod parser.Model) {
82+
/* =========================
83+
Orchestration
84+
========================= */
85+
86+
func writeOut(outputPath, varName, genFnName, currentPkgPath string, mod parser.Model) error {
6387
cfg := render.Config{
6488
IsVarBased: varName != "",
6589
VarName: varName,
@@ -69,53 +93,49 @@ func writeOut(outputPath, varName, genFnName, currentPkgPath string, mod parser.
6993
CurrentPkgPath: currentPkgPath,
7094
}
7195

72-
src, emitErr := render.EmitWithFunc(mod, cfg)
73-
if emitErr != nil {
74-
log.Fatalf("render: %v", emitErr)
96+
src, err := render.EmitWithFunc(mod, cfg)
97+
if err != nil {
98+
return fmt.Errorf("render: %w", err)
7599
}
76100

77-
if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
78-
log.Fatalf("mkdir: %v", err)
101+
if mkdirErr := os.MkdirAll(safeDir(outputPath), 0o755); mkdirErr != nil {
102+
return fmt.Errorf("mkdir: %w", mkdirErr)
79103
}
80-
if err := os.WriteFile(outputPath, []byte(src), 0o644); err != nil {
81-
log.Fatalf("write: %v", err)
104+
if writeErr := os.WriteFile(outputPath, []byte(src), 0o644); writeErr != nil {
105+
return fmt.Errorf("write: %w", writeErr)
82106
}
107+
83108
fmt.Printf("assemblegen: wrote %s (%d bytes)\n", outputPath, len(src))
109+
return nil
84110
}
85111

86112
func extractFromFunctionTwoPass(pkgArg, funcName string, fset *token.FileSet) (parser.Model, string, string, error) {
113+
// Pass A: syntax-only (with build tag) to avoid referencing generated code.
87114
cfgA := loadConfigSyntaxOnly(dirFor(pkgArg), []string{"-tags=assemble_codegen"}, fset)
88-
pkgsA, err := packages.Load(cfgA, pkgArg)
115+
pA, err := loadOnePackage(cfgA, pkgArg, "load (A)")
89116
if err != nil {
90-
return parser.Model{}, "", "", fmt.Errorf("load (A): %w", err)
117+
return parser.Model{}, "", "", err
91118
}
92-
if len(pkgsA) == 0 {
93-
return parser.Model{}, "", "", fmt.Errorf("failed loading package (A) %q", pkgArg)
94-
}
95-
pA := pkgsA[0]
96119

97120
ref, genFnName, refErr := parser.ExtractFunctionModuleRefLoose(pA, funcName, fset)
98121
if refErr != nil {
99122
return parser.Model{}, "", "", refErr
100123
}
101124

125+
// Pass B: full typed load.
102126
cfgB := loadConfig(dirFor(pkgArg), nil, fset)
103127

104128
switch {
105129
case ref.IsLocalIdent:
106-
pkgsB, err := packages.Load(cfgB, pkgArg)
107-
if err != nil {
108-
return parser.Model{}, "", "", fmt.Errorf("load (B local): %w", err)
130+
pB, pBErr := loadOnePackage(cfgB, pkgArg, "load (B local)")
131+
if pBErr != nil {
132+
return parser.Model{}, "", "", pBErr
109133
}
110-
if packages.PrintErrors(pkgsB) > 0 || len(pkgsB) == 0 {
111-
return parser.Model{}, "", "", fmt.Errorf("failed loading package (B local) %q", pkgArg)
134+
mod, modErr := parser.ExtractModule(pB, ref.Ident, fset)
135+
if modErr != nil {
136+
return parser.Model{}, "", "", fmt.Errorf("extract local module %q: %w", ref.Ident, modErr)
112137
}
113-
pB := pkgsB[0]
114-
mod, err := parser.ExtractModule(pB, ref.Ident, fset)
115-
if err != nil {
116-
return parser.Model{}, "", "", fmt.Errorf("extract local module %q: %w", ref.Ident, err)
117-
}
118-
// merge function-file imports so renderer can include them
138+
// Merge function-file imports so renderer can include them.
119139
mod.Imports = append(mod.Imports, ref.Imports...)
120140
mod.SetPkgName(pB.Name)
121141
return mod, pB.PkgPath, genFnName, nil
@@ -124,17 +144,15 @@ func extractFromFunctionTwoPass(pkgArg, funcName string, fset *token.FileSet) (p
124144
if ref.SelPkgPath == "" {
125145
return parser.Model{}, "", "", fmt.Errorf("could not resolve import path for alias %q", ref.SelPkgAlias)
126146
}
127-
pkgsB, err := packages.Load(cfgB, ref.SelPkgPath)
128-
if err != nil {
129-
return parser.Model{}, "", "", fmt.Errorf("load (B foreign %q): %w", ref.SelPkgPath, err)
147+
q, qErr := loadOnePackage(cfgB, ref.SelPkgPath, "load (B foreign)")
148+
if qErr != nil {
149+
return parser.Model{}, "", "", qErr
130150
}
131-
if packages.PrintErrors(pkgsB) > 0 || len(pkgsB) == 0 {
132-
return parser.Model{}, "", "", fmt.Errorf("failed loading foreign package %q", ref.SelPkgPath)
133-
}
134-
q := pkgsB[0]
135-
mod, err := parser.ExtractModule(q, ref.SelIdent, fset)
136-
if err != nil {
137-
return parser.Model{}, "", "", fmt.Errorf("extract foreign module %s.%s: %w", ref.SelPkgPath, ref.SelIdent, err)
151+
mod, modErr := parser.ExtractModule(q, ref.SelIdent, fset)
152+
if modErr != nil {
153+
return parser.Model{},
154+
"", "",
155+
fmt.Errorf("extract foreign module %s.%s: %w", ref.SelPkgPath, ref.SelIdent, modErr)
138156
}
139157
mod.Imports = append(mod.Imports, ref.Imports...)
140158
mod.SetPkgName(q.Name)
@@ -144,6 +162,21 @@ func extractFromFunctionTwoPass(pkgArg, funcName string, fset *token.FileSet) (p
144162
return parser.Model{}, "", "", errors.New("unsupported module reference shape")
145163
}
146164

165+
/* =========================
166+
packages helpers
167+
========================= */
168+
169+
func loadOnePackage(cfg *packages.Config, pattern string, ctx string) (*packages.Package, error) {
170+
pkgs, err := packages.Load(cfg, pattern)
171+
if err != nil {
172+
return nil, fmt.Errorf("%s: %w", ctx, err)
173+
}
174+
if packages.PrintErrors(pkgs) > 0 || len(pkgs) == 0 {
175+
return nil, fmt.Errorf("failed %s %q", ctx, pattern)
176+
}
177+
return pkgs[0], nil
178+
}
179+
147180
func loadConfig(dir string, buildFlags []string, fset *token.FileSet) *packages.Config {
148181
cfg := &packages.Config{
149182
Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
@@ -170,10 +203,28 @@ func loadConfigSyntaxOnly(dir string, buildFlags []string, fset *token.FileSet)
170203
return cfg
171204
}
172205

206+
/* =========================
207+
small utils
208+
========================= */
209+
210+
// dirFor returns the directory to set in packages.Config for relative patterns.
211+
// Keeps original behavior but accepts "." and "./...".
173212
func dirFor(arg string) string {
174-
if len(arg) >= 2 && arg[:2] == "./" {
213+
if arg == "." || hasDotSlashPrefix(arg) {
175214
wd, _ := os.Getwd()
176215
return wd
177216
}
178217
return ""
179218
}
219+
220+
func hasDotSlashPrefix(s string) bool {
221+
return len(s) >= 2 && s[:2] == "./"
222+
}
223+
224+
func safeDir(p string) string {
225+
d := filepath.Dir(p)
226+
if d == "" || d == "." {
227+
return "."
228+
}
229+
return d
230+
}

0 commit comments

Comments
 (0)