@@ -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+
1829func 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
86112func 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+
147180func 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 "./...".
173212func 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