template / windows / files / main.go
Branch windows · default branch · all branches
main.go
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "os" |
| 6 | "os/user" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | |
| 10 | log "github.com/sirupsen/logrus" |
| 11 | "github.com/urfave/cli/v2" // imports as package "cli" |
| 12 | "gopkg.in/yaml.v3" |
| 13 | ) |
| 14 | |
| 15 | const defaultUserConfigPath = "~/.template.user.yaml" |
| 16 | const defaultConfigPath = "~/.template.yaml" |
| 17 | const defaultLogLevel = "info" |
| 18 | |
| 19 | type ArgumentValues struct { |
| 20 | profile string |
| 21 | projectName string |
| 22 | configFilePath string |
| 23 | userConfigPath string |
| 24 | extrasFilePath string |
| 25 | logLevel string |
| 26 | } |
| 27 | type EnvVarValues struct { |
| 28 | useDefaultConfig bool |
| 29 | } |
| 30 | type Settings struct { |
| 31 | ArgumentValues |
| 32 | EnvVarValues |
| 33 | extras map[string]string |
| 34 | fullConfigPath string |
| 35 | config Config |
| 36 | usr *user.User |
| 37 | } |
| 38 | |
| 39 | func main() { |
| 40 | // var err error |
| 41 | // var usr *user.User |
| 42 | argumentValues := ArgumentValues{} |
| 43 | |
| 44 | app := &cli.App{ |
| 45 | Name: "template", |
| 46 | Usage: "create file or directory from a template", |
| 47 | EnableBashCompletion: true, |
| 48 | Suggest: true, |
| 49 | Flags: []cli.Flag{ |
| 50 | &cli.StringFlag{ |
| 51 | Name: "profile", |
| 52 | Aliases: []string{"p"}, |
| 53 | Value: "", |
| 54 | Usage: "`PROFILE` to use for the template", |
| 55 | Destination: &argumentValues.profile, |
| 56 | // Value: defaultProfileName, |
| 57 | // Required: true, |
| 58 | // EnvVars: []string{"TEMPLATE_PROFILE"}, |
| 59 | }, |
| 60 | &cli.StringFlag{ |
| 61 | Name: "name", |
| 62 | Aliases: []string{"n"}, |
| 63 | Usage: "name of the project to create", |
| 64 | Value: "", |
| 65 | Destination: &argumentValues.projectName, |
| 66 | }, |
| 67 | &cli.PathFlag{ |
| 68 | Name: "config-yaml", |
| 69 | Aliases: []string{"c"}, |
| 70 | Usage: "path to the config yaml file", |
| 71 | Value: defaultConfigPath, |
| 72 | Destination: &argumentValues.configFilePath, |
| 73 | }, |
| 74 | &cli.PathFlag{ |
| 75 | Name: "user-config-yaml", |
| 76 | Aliases: []string{"u"}, |
| 77 | Usage: "path to the user config yaml file", |
| 78 | Value: defaultUserConfigPath, |
| 79 | Destination: &argumentValues.userConfigPath, |
| 80 | }, |
| 81 | &cli.PathFlag{ |
| 82 | Name: "extras-yaml", |
| 83 | Aliases: []string{"e"}, |
| 84 | Usage: "path to the extras yaml file", |
| 85 | Value: "~/.template-extras.yaml", |
| 86 | Destination: &argumentValues.extrasFilePath, |
| 87 | }, |
| 88 | &cli.StringFlag{ |
| 89 | Name: "log-level", |
| 90 | Aliases: []string{"l"}, |
| 91 | Usage: "log level", |
| 92 | Value: defaultLogLevel, |
| 93 | Destination: &argumentValues.logLevel, |
| 94 | }, |
| 95 | }, |
| 96 | Commands: []*cli.Command{ |
| 97 | { |
| 98 | Name: "list", |
| 99 | Usage: "list available profiles", |
| 100 | Action: func(c *cli.Context) error { |
| 101 | settings, err := createSettings(argumentValues, c) |
| 102 | if err != nil { |
| 103 | return fmt.Errorf("error creating settings: %v", err) |
| 104 | } |
| 105 | |
| 106 | config := settings.config |
| 107 | // list profiles |
| 108 | fmt.Println("Available profiles:") |
| 109 | for _, p := range config.Profiles { |
| 110 | fmt.Printf(" %v: %v\n", p.Name, p.Description) |
| 111 | } |
| 112 | return nil |
| 113 | }, |
| 114 | }, |
| 115 | { |
| 116 | Name: "get", |
| 117 | Usage: "get a profile", |
| 118 | Action: func(c *cli.Context) error { |
| 119 | settings, err := createSettings(argumentValues, c) |
| 120 | if err != nil { |
| 121 | return fmt.Errorf("error creating settings: %v", err) |
| 122 | } |
| 123 | config := settings.config |
| 124 | |
| 125 | // get profile information |
| 126 | index, err := findProfileIndex(config.Profiles, settings.profile) |
| 127 | if err != nil { |
| 128 | return fmt.Errorf("profile '%v' not found", settings.profile) |
| 129 | } |
| 130 | profile := config.Profiles[index] |
| 131 | |
| 132 | writer := strings.Builder{} |
| 133 | encoder := yaml.NewEncoder(&writer) |
| 134 | err = encoder.Encode(profile) |
| 135 | if err != nil { |
| 136 | return fmt.Errorf("error encoding profile: %v", err) |
| 137 | } |
| 138 | |
| 139 | yamlText := writer.String() |
| 140 | fmt.Printf("Profile '%v':\n", settings.profile) |
| 141 | fmt.Println(yamlText) |
| 142 | return nil |
| 143 | }, |
| 144 | }, |
| 145 | { |
| 146 | Name: "reset", |
| 147 | Usage: "reset the global template config to default profiles", |
| 148 | Action: func(c *cli.Context) error { |
| 149 | settings, err := createSettings(argumentValues, c) |
| 150 | if err != nil { |
| 151 | return fmt.Errorf("error creating settings: %v", err) |
| 152 | } |
| 153 | fullConfigPath := settings.fullConfigPath |
| 154 | |
| 155 | fmt.Printf("Resetting config at '%v' to default\n", fullConfigPath) |
| 156 | err = saveConfig(fullConfigPath, defaultConfig()) |
| 157 | if err != nil { |
| 158 | return fmt.Errorf("error saving default config: %v", err) |
| 159 | } |
| 160 | return nil |
| 161 | }, |
| 162 | }, |
| 163 | }, |
| 164 | Action: func(context *cli.Context) error { |
| 165 | settings, err := createSettings(argumentValues, context) |
| 166 | if err != nil { |
| 167 | return fmt.Errorf("error parsing arguments: %w", err) |
| 168 | } |
| 169 | err = mainLogic(settings) |
| 170 | if err != nil { |
| 171 | return fmt.Errorf("error in main logic: %w", err) |
| 172 | } |
| 173 | return nil |
| 174 | }, |
| 175 | } |
| 176 | |
| 177 | if err := app.Run(os.Args); err != nil { |
| 178 | log.Fatal(err) |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | func createSettings( |
| 183 | argumentValues ArgumentValues, |
| 184 | context *cli.Context, |
| 185 | ) (Settings, error) { |
| 186 | envVarValues, err := readEnvVars() |
| 187 | if err != nil { |
| 188 | return Settings{}, fmt.Errorf("error getting env var values: %w", err) |
| 189 | } |
| 190 | settings := Settings{ |
| 191 | ArgumentValues: argumentValues, |
| 192 | EnvVarValues: envVarValues, |
| 193 | } |
| 194 | |
| 195 | // read args |
| 196 | args := context.Args() |
| 197 | |
| 198 | // configure logging |
| 199 | switch settings.logLevel { |
| 200 | case "debug": |
| 201 | log.SetLevel(log.DebugLevel) |
| 202 | case "info": |
| 203 | log.SetLevel(log.InfoLevel) |
| 204 | case "warn": |
| 205 | log.SetLevel(log.WarnLevel) |
| 206 | case "error": |
| 207 | log.SetLevel(log.ErrorLevel) |
| 208 | case "fatal": |
| 209 | log.SetLevel(log.FatalLevel) |
| 210 | case "panic": |
| 211 | log.SetLevel(log.PanicLevel) |
| 212 | default: |
| 213 | fmt.Printf("invalid log level specified %v, using info", settings.logLevel) |
| 214 | log.SetLevel(log.InfoLevel) |
| 215 | } |
| 216 | log.SetOutput(os.Stdout) |
| 217 | // log.SetFormatter(&log.JSONFormatter{}) |
| 218 | |
| 219 | log.WithFields(log.Fields{"args": args}).Debug("Args parsed") |
| 220 | |
| 221 | // get profile name from args |
| 222 | // if not set explicitly, use first arg, if present |
| 223 | if settings.profile == "" && args.Len() > 0 { |
| 224 | settings.profile = args.Get(0) |
| 225 | } |
| 226 | fmt.Println("Profile:", settings.profile) |
| 227 | |
| 228 | // get project name from args |
| 229 | // if not set explicitly, use second arg, if present |
| 230 | if settings.projectName == "" && args.Len() > 1 { |
| 231 | settings.projectName = args.Get(1) |
| 232 | } |
| 233 | fmt.Println("Project name:", settings.projectName) |
| 234 | |
| 235 | // extras strings |
| 236 | _, err = os.Stat(settings.extrasFilePath) |
| 237 | if err == nil { |
| 238 | extras, err := loadExtras(settings.extrasFilePath) |
| 239 | if err != nil { |
| 240 | return settings, fmt.Errorf("error loading extras from %v: %w", settings.extrasFilePath, err) |
| 241 | } |
| 242 | settings.extras = extras |
| 243 | } |
| 244 | |
| 245 | // get user |
| 246 | usr, err := user.Current() |
| 247 | if err != nil { |
| 248 | log.Fatal(err) |
| 249 | } |
| 250 | settings.usr = usr |
| 251 | |
| 252 | // get config from default path |
| 253 | fullConfigPath := getFullPath(settings.configFilePath, *usr) |
| 254 | settings.fullConfigPath = fullConfigPath |
| 255 | |
| 256 | var config Config |
| 257 | if settings.useDefaultConfig { |
| 258 | config = defaultConfig() |
| 259 | } else { |
| 260 | config, err = loadConfig(fullConfigPath) |
| 261 | if err != nil { |
| 262 | return settings, fmt.Errorf("error loading config from %v: %w", fullConfigPath, err) |
| 263 | } else { |
| 264 | fmt.Printf("Loaded config from %v\n", fullConfigPath) |
| 265 | } |
| 266 | // load user config, if present |
| 267 | fullUserConfigPath := getFullPath(settings.userConfigPath, *usr) |
| 268 | _, err = os.Stat(fullUserConfigPath) |
| 269 | if err == nil { |
| 270 | userConfig, err := loadConfig(fullUserConfigPath) |
| 271 | if err != nil { |
| 272 | return settings, fmt.Errorf("error loading user config from %v: %w", fullUserConfigPath, err) |
| 273 | } else { |
| 274 | fmt.Printf("Loaded user config from %v, combining with global config\n", fullUserConfigPath) |
| 275 | config, err = mergeConfigs(config, userConfig) |
| 276 | if err != nil { |
| 277 | return settings, fmt.Errorf("error merging configs: %w", err) |
| 278 | } |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | settings.config = config |
| 283 | |
| 284 | return settings, nil |
| 285 | } |
| 286 | |
| 287 | func readEnvVars() (EnvVarValues, error) { |
| 288 | envVarValues := EnvVarValues{} |
| 289 | |
| 290 | useDefaultConfigText := os.Getenv("TEMPLATE_USE_DEFAULT_CONFIG") |
| 291 | if useDefaultConfigText != "" { |
| 292 | useDefaultConfig, err := strconv.ParseBool(useDefaultConfigText) |
| 293 | if err != nil { |
| 294 | return envVarValues, fmt.Errorf("error parsing env var TEMPLATE_USE_DEFAULT_CONFIG: \"%s\" %w", useDefaultConfigText, err) |
| 295 | } |
| 296 | envVarValues.useDefaultConfig = useDefaultConfig |
| 297 | } |
| 298 | |
| 299 | return envVarValues, nil |
| 300 | } |
| 301 | |
| 302 | func findProfile(settings Settings) (Profile, error) { |
| 303 | var profile Profile = Profile{} |
| 304 | var err error |
| 305 | config := settings.config |
| 306 | if settings.profile == "" { |
| 307 | profile, err = findDefaultProfile(config.Profiles) |
| 308 | if err != nil { |
| 309 | return profile, fmt.Errorf("no profile specified and no default profile found") |
| 310 | } |
| 311 | } else { |
| 312 | index, err := findProfileIndex(config.Profiles, settings.profile) |
| 313 | if err != nil { |
| 314 | return profile, fmt.Errorf("error finding profile: %v", err) |
| 315 | } |
| 316 | profile = config.Profiles[index] |
| 317 | } |
| 318 | return profile, nil |
| 319 | } |
| 320 | |
| 321 | func mainLogic(settings Settings) error { |
| 322 | // find profile |
| 323 | profile, err := findProfile(settings) |
| 324 | if err != nil { |
| 325 | return fmt.Errorf("error finding profile: %v", err) |
| 326 | } |
| 327 | |
| 328 | // process profile |
| 329 | errors := processProfile(profile, settings.projectName, settings.extras, *settings.usr) |
| 330 | if len(errors) > 0 { |
| 331 | return fmt.Errorf("error processing profile: %v", errors) |
| 332 | } |
| 333 | |
| 334 | return nil |
| 335 | } |