github.com/argots/stencil@v0.0.2/pkg/stencil/stencil.go (about) 1 // Package stencil implements a template package manager. 2 package stencil 3 4 import ( 5 "bytes" 6 "errors" 7 "flag" 8 "os" 9 "text/template" 10 ) 11 12 // Logger is the interface used by stencil to log messages. 13 // 14 // Use log.New(...) to create an appropriate logger. 15 type Logger interface { 16 Printf(format string, v ...interface{}) 17 } 18 19 // FileSystem is the generic file system used by stencil. 20 // Use FS{} or a custom implementation. 21 type FileSystem interface { 22 Read(path string) ([]byte, error) 23 Write(path string, data []byte, mode os.FileMode) error 24 Remove(path string) error 25 RemoveAll(path string) error 26 } 27 28 // Prompter is the generic interface to prompt and fetch info 29 // interactively. 30 type Prompter interface { 31 PromptBool(prompt string) (bool, error) 32 PromptString(prompt string) (string, error) 33 } 34 35 // New creates a new stencil manager. 36 func New(verbose, errorl Logger, p Prompter, fs FileSystem) *Stencil { 37 s := &Stencil{ 38 State: map[string]interface{}{}, 39 Funcs: map[string]interface{}{}, 40 Printf: verbose.Printf, 41 Errorf: func(fmt string, v ...interface{}) error { 42 errorl.Printf(fmt, v...) 43 err, _ := v[len(v)-1].(error) 44 return err 45 }, 46 FileSystem: fs, 47 Prompter: p, 48 Binary: Binary{}, 49 Objects: Objects{ 50 Before: &Objects{}, 51 Pulls: map[string]bool{}, 52 Files: map[string]*FileObj{}, 53 FileArchives: map[string]*FileArchiveObj{}, 54 Bools: map[string]bool{}, 55 Strings: map[string]string{}, 56 }, 57 Vars: Vars{ 58 BoolDefs: map[string]string{}, 59 StringDefs: map[string]string{}, 60 }, 61 Markdown: Markdown{}, 62 } 63 s.Binary.Stencil = s 64 s.Objects.Stencil = s 65 s.Vars.Stencil = s 66 s.Markdown.Stencil = s 67 s.Funcs["stencil"] = func() interface{} { 68 return s 69 } 70 return s 71 } 72 73 // Stencil maintains all the state for managing a single directory. 74 type Stencil struct { 75 State map[string]interface{} 76 Funcs map[string]interface{} 77 Printf func(format string, v ...interface{}) 78 Errorf func(format string, v ...interface{}) error 79 FileSystem 80 Prompter 81 Env 82 Binary 83 Objects 84 Vars 85 Markdown 86 } 87 88 // Main implements the main program. 89 func (s *Stencil) Main(f *flag.FlagSet, args []string) error { 90 errMissingArg := errors.New("missing arg") 91 f.Usage = func() { 92 s.Printf(`Usage: stencil [options] commands 93 commands: 94 pull url_or_file -- add url to pulls and sync 95 rm url_or_fil -- remove url from pulls and sync 96 sync -- update all existing pulls 97 `) 98 f.PrintDefaults() 99 } 100 s.Vars.Init(f) 101 if err := f.Parse(args[1:]); err != nil { 102 return s.Errorf("flagset parse", err) 103 } 104 105 switch f.Arg(0) { 106 case "pull": 107 if f.Arg(1) != "" { 108 s.Printf("Adding %s\n", f.Arg(1)) 109 return s.run(f.Arg(1), "") 110 } 111 return s.Errorf("pull requires a url or path to a recipe %v\n", errMissingArg) 112 case "sync": 113 s.Printf("Updating all pulled recipes\n") 114 return s.run("", "") 115 case "rm": 116 if f.Arg(1) != "" { 117 s.Printf("Removing %s\n", f.Arg(1)) 118 return s.run("", f.Arg(1)) 119 } 120 return s.Errorf("rm requires a url or path to a recipe %v\n", errMissingArg) 121 case "": 122 f.Usage() 123 return nil 124 } 125 return s.Errorf("%v", errors.New("unknown command: "+f.Arg(0))) 126 } 127 128 func (s *Stencil) run(add, rm string) error { 129 if err := s.Objects.LoadObjects(); err != nil { 130 return s.Errorf("LoadObjects %v\n", err) 131 } 132 for pull := range s.Before.Pulls { 133 if pull != rm { 134 s.Objects.addPull(pull) 135 } 136 } 137 if add != "" { 138 s.Objects.addPull(add) 139 } 140 for pull := range s.Pulls { 141 s.Printf("Pulling %s\n", pull) 142 if err := s.Run(pull); err != nil { 143 return s.Errorf("Run: %v\n", err) 144 } 145 } 146 if err := s.GC(); err != nil { 147 return s.Errorf("GC %v\n", err) 148 } 149 150 return s.SaveObjects() 151 } 152 153 // CopyFile copies a url to a local file. 154 func (s *Stencil) CopyFile(key, localPath, url string) error { 155 s.Printf("copying %s to %s, key (%s)\n", url, localPath, key) 156 s.Objects.addFile(key, localPath, url) 157 158 data, err := s.Execute(url) 159 if err != nil { 160 return s.Errorf("Error reading %s %v\n", url, err) 161 } 162 return s.Write(localPath, []byte(data), 0666) 163 } 164 165 // Run runs a template discarding the output. 166 func (s *Stencil) Run(source string) error { 167 _, err := s.Import(source) 168 return err 169 } 170 171 // Import imports a template after applying it. 172 func (s *Stencil) Import(source string) (string, error) { 173 s.Printf("Running import %s\n", source) 174 return s.Execute(source) 175 } 176 177 // Execute is like Run but it returns the output. 178 func (s *Stencil) Execute(source string) (string, error) { 179 return s.executeFilter(source, nil) 180 } 181 182 func (s *Stencil) executeFilter(source string, filter func(string) (string, error)) (string, error) { 183 s.Printf("Executing %s\n", source) 184 185 data, err := s.Read(source) 186 if err != nil { 187 return "", s.Errorf("Error reading %s: %v\n", source, err) 188 } 189 190 if filter != nil { 191 str, err := filter(string(data)) 192 if err != nil { 193 return "", s.Errorf("Error filtering %s: %v", source, err) 194 } 195 data = []byte(str) 196 } 197 198 t, err := template.New(source).Funcs(s.Funcs).Parse(string(data)) 199 if err != nil { 200 return "", s.Errorf("Error parsing %s: %v\n", source, err) 201 } 202 203 var buf bytes.Buffer 204 err = t.Execute(&buf, s.State) 205 if err != nil { 206 return "", s.Errorf("Error executing %s: %v\n", source, err) 207 } 208 209 return buf.String(), nil 210 }