1+ package cmd
2+
3+ import (
4+ "errors"
5+ "fmt"
6+ "html/template"
7+ "io/fs"
8+ "os"
9+ "path/filepath"
10+ "regexp"
11+ "slices"
12+ "strings"
13+
14+ "github.com/codeshelldev/goplater/utils/fsutils"
15+ "github.com/codeshelldev/goplater/utils/get"
16+ "github.com/codeshelldev/goplater/utils/templating"
17+ "github.com/spf13/cobra"
18+ )
19+
20+ var context string = "."
21+ var output string = "."
22+ var localContext string = "."
23+
24+ var whitespace []string
25+ var match []string
26+
27+ var verbose bool
28+ var recursive bool
29+ var preserveStructure bool
30+
31+ var templateCmd = & cobra.Command {
32+ Use : "template" ,
33+ Short : "Template files" ,
34+ Args : validate ,
35+ Long : `Template files by using local or remote files.` ,
36+ Run : run ,
37+ }
38+
39+ func validate (cmd * cobra.Command , args []string ) error {
40+ if len (args ) <= 0 && ! recursive {
41+ return errors .New ("not enough args" )
42+ } else if len (args ) > 0 {
43+ _ , err := os .Stat (args [0 ])
44+
45+ if errors .Is (err , os .ErrNotExist ) {
46+ return errors .New ("invalid context path" )
47+ }
48+ }
49+
50+ return nil
51+ }
52+
53+ func init () {
54+ rootCmd .AddCommand (templateCmd )
55+
56+ templateCmd .Flags ().BoolVarP (& recursive , "recursive" , "r" , false , "recusively template files" )
57+ templateCmd .Flags ().BoolVarP (& verbose , "verbose" , "v" , false , "print additional information" )
58+ templateCmd .Flags ().BoolVarP (& preserveStructure , "preserve-struct" , "p" , true , "preserves source folder structure in output path" )
59+
60+ templateCmd .Flags ().StringVarP (& output , "output" , "o" , "." , "output path for templated files" )
61+ templateCmd .Flags ().StringVarP (& localContext , "source" , "s" , "." , "source path for local files" )
62+
63+ templateCmd .Flags ().StringSliceVarP (& match , "match" , "m" , []string {".*" }, "regex match for templating" )
64+
65+ templateCmd .Flags ().StringSliceVarP (& whitespace , "whitespace" , "w" , []string {"l" ,"t" }, "remove whitespace from files" )
66+ }
67+
68+ func run (cmd * cobra.Command , args []string ) {
69+ context = args [0 ]
70+
71+ fullPath , _ := filepath .Abs (context )
72+
73+ isDir := fsutils .IsDir (fullPath )
74+ isFile := fsutils .IsFile (fullPath )
75+
76+ if isDir {
77+ localContext = context
78+
79+ filepath .WalkDir (context , func (path string , d fs.DirEntry , err error ) error {
80+ if err != nil {
81+ return err
82+ }
83+
84+ if ! d .IsDir () {
85+ handleFile (path )
86+ } else if path != context && ! recursive {
87+ return filepath .SkipDir
88+ }
89+
90+ return nil
91+ })
92+ } else if isFile {
93+ handleFile (context )
94+ }
95+ }
96+
97+ func matchFile (path string ) bool {
98+ fileName := filepath .Base (path )
99+
100+ for _ , reStr := range match {
101+ re , err := regexp .Compile (reStr )
102+
103+ if err == nil {
104+ if re .MatchString (fileName ) {
105+ return true
106+ }
107+ }
108+ }
109+
110+ return false
111+ }
112+
113+ func handleFile (relativePath string ) {
114+ if ! matchFile (relativePath ) {
115+ if verbose {
116+ fmt .Println ("skipped" , relativePath )
117+ }
118+
119+ return
120+ }
121+
122+ if verbose {
123+ fmt .Println ("templating" , relativePath )
124+ }
125+
126+ err := templateFile (relativePath )
127+
128+ if err != nil {
129+ fmt .Println ("error templating:" , err .Error ())
130+ }
131+ }
132+
133+ func templateFile (relativePath string ) error {
134+ if ! fsutils .IsFile (relativePath ) {
135+ return os .ErrNotExist
136+ }
137+
138+ data , err := os .ReadFile (relativePath )
139+
140+ if err != nil {
141+ return err
142+ }
143+
144+ if data == nil {
145+ return errors .New ("empty file" )
146+ }
147+
148+ normalized := normalize (string (data ))
149+
150+ tmplStr , err := templateStr ("main" , normalized , nil )
151+
152+ if err != nil {
153+ return err
154+ }
155+
156+ return handleFileWrite (tmplStr , relativePath )
157+ }
158+
159+ func handleFileWrite (content , relativePath string ) error {
160+ fullPath := fsutils .ResolveOutput (relativePath , output , preserveStructure )
161+
162+ if verbose {
163+ fmt .Println ("writing to" , fullPath )
164+ }
165+
166+ dir := filepath .Dir (fullPath )
167+ err := os .MkdirAll (dir , 0755 );
168+
169+ if err != nil {
170+ return err
171+ }
172+
173+ err = os .WriteFile (fullPath , []byte (content ), 0644 )
174+
175+ if err != nil {
176+ return err
177+ }
178+
179+ return nil
180+ }
181+
182+ func normalize (content string ) string {
183+ normalizeLocal , err := templating .ReplaceTemplatePrefix (content , `#://` , "#://" )
184+
185+ if err == nil {
186+ content = normalizeLocal
187+ }
188+
189+ normalizeRemote , err := templating .ReplaceTemplatePrefix (content , `@://` , "@://" )
190+
191+ if err == nil {
192+ content = normalizeRemote
193+ }
194+
195+ return content
196+ }
197+
198+ func removeWhitespace (r rune ) bool {
199+ return r == ' ' || r == '\t' || r == '\n' || r == '\r'
200+ }
201+
202+ func templateGet (key string ) any {
203+ var res string
204+
205+ switch (key [:4 ]) {
206+ case "@://" :
207+ res = get .Remote (key [4 :])
208+ case "#://" :
209+ res = get .Local (key [4 :], localContext )
210+ }
211+
212+ if slices .Contains (whitespace , "l" ) {
213+ res = strings .TrimLeftFunc (res , removeWhitespace )
214+ }
215+
216+ if slices .Contains (whitespace , "t" ) {
217+ res = strings .TrimRightFunc (res , removeWhitespace )
218+ }
219+
220+ return res
221+ }
222+
223+ func templateStr (name , str string , variables map [string ]any ) (string , error ) {
224+ tmplStr , err := templating .AddTemplateFunc (str , "get" )
225+
226+ if err != nil {
227+ return str , err
228+ }
229+
230+ templt := templating .CreateTemplateWithFunc (name , template.FuncMap {
231+ "get" : templateGet ,
232+ })
233+
234+ tmplStr , err = templating .ParseTemplate (templt , tmplStr , variables )
235+
236+ if err != nil {
237+ return str , err
238+ }
239+
240+ return tmplStr , nil
241+ }
0 commit comments