template / windows / files / main.go
Branch windows · default branch · all branches
main.go
f819f801 2022-09-21 Pavel Safronov | 1 | package main |
| 2 | ||
| 3 | import ( | |
| 4 | "fmt" | |
| 5 | "os" | |
05f9e8ef 2022-10-06 Pavel Safronov | 6 | "os/user" |
9573ca02 2022-11-16 Pavel Safronov | 7 | "strconv" |
4a7759db 2022-09-24 Pavel Safronov | 8 | "strings" |
f819f801 2022-09-21 Pavel Safronov | 9 | |
886f7fa2 2022-11-02 Pavel Safronov | 10 | log "github.com/sirupsen/logrus" |
f819f801 2022-09-21 Pavel Safronov | 11 | "github.com/urfave/cli/v2" // imports as package "cli" |
4a7759db 2022-09-24 Pavel Safronov | 12 | "gopkg.in/yaml.v3" |
f819f801 2022-09-21 Pavel Safronov | 13 | ) |
| 14 | ||
ae89a42a 2022-11-02 Pavel Safronov | 15 | const defaultUserConfigPath = "~/.template.user.yaml" |
| 16 | const defaultConfigPath = "~/.template.yaml" | |
886f7fa2 2022-11-02 Pavel Safronov | 17 | const defaultLogLevel = "info" |
ae89a42a 2022-11-02 Pavel Safronov | 18 | |
b1ded4b4 2022-10-30 Pavel Safronov | 19 | type ArgumentValues struct { |
ae89a42a 2022-11-02 Pavel Safronov | 20 | profile string |
| 21 | projectName string | |
| 22 | configFilePath string | |
| 23 | userConfigPath string | |
| 24 | extrasFilePath string | |
886f7fa2 2022-11-02 Pavel Safronov | 25 | logLevel string |
9573ca02 2022-11-16 Pavel Safronov | 26 | } |
| 27 | type EnvVarValues struct { | |
| 28 | useDefaultConfig bool | |
b1ded4b4 2022-10-30 Pavel Safronov | 29 | } |
| 30 | type Settings struct { | |
| 31 | ArgumentValues | |
9573ca02 2022-11-16 Pavel Safronov | 32 | EnvVarValues |
8c15a714 2022-11-04 Pavel Safronov | 33 | extras map[string]string |
| 34 | fullConfigPath string | |
| 35 | config Config | |
| 36 | usr *user.User | |
b1ded4b4 2022-10-30 Pavel Safronov | 37 | } |
| 38 | ||
f819f801 2022-09-21 Pavel Safronov | 39 | func main() { |
b1ded4b4 2022-10-30 Pavel Safronov | 40 | // var err error |
| 41 | // var usr *user.User | |
| 42 | argumentValues := ArgumentValues{} | |
f819f801 2022-09-21 Pavel Safronov | 43 | |
| 44 | app := &cli.App{ | |
| 45 | Name: "template", | |
| 46 | Usage: "create file or directory from a template", | |
| 47 | EnableBashCompletion: true, | |
185f54e8 2022-10-05 Pavel Safronov | 48 | Suggest: true, |
f819f801 2022-09-21 Pavel Safronov | 49 | Flags: []cli.Flag{ |
| 50 | &cli.StringFlag{ | |
ea896f47 2022-09-23 Pavel Safronov | 51 | Name: "profile", |
| 52 | Aliases: []string{"p"}, | |
| 53 | Value: "", | |
f819f801 2022-09-21 Pavel Safronov | 54 | Usage: "`PROFILE` to use for the template", |
b1ded4b4 2022-10-30 Pavel Safronov | 55 | Destination: &argumentValues.profile, |
ea896f47 2022-09-23 Pavel Safronov | 56 | // Value: defaultProfileName, |
f819f801 2022-09-21 Pavel Safronov | 57 | // Required: true, |
| 58 | // EnvVars: []string{"TEMPLATE_PROFILE"}, | |
| 59 | }, | |
d026838a 2022-09-22 Pavel Safronov | 60 | &cli.StringFlag{ |
| 61 | Name: "name", | |
| 62 | Aliases: []string{"n"}, | |
| 63 | Usage: "name of the project to create", | |
| 64 | Value: "", | |
b1ded4b4 2022-10-30 Pavel Safronov | 65 | Destination: &argumentValues.projectName, |
d026838a 2022-09-22 Pavel Safronov | 66 | }, |
185f54e8 2022-10-05 Pavel Safronov | 67 | &cli.PathFlag{ |
| 68 | Name: "config-yaml", | |
| 69 | Aliases: []string{"c"}, | |
| 70 | Usage: "path to the config yaml file", | |
ae89a42a 2022-11-02 Pavel Safronov | 71 | Value: defaultConfigPath, |
b1ded4b4 2022-10-30 Pavel Safronov | 72 | Destination: &argumentValues.configFilePath, |
185f54e8 2022-10-05 Pavel Safronov | 73 | }, |
ae89a42a 2022-11-02 Pavel Safronov | 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 | }, | |
185f54e8 2022-10-05 Pavel Safronov | 81 | &cli.PathFlag{ |
55c36f6a 2022-10-05 Pavel Safronov | 82 | Name: "extras-yaml", |
| 83 | Aliases: []string{"e"}, | |
| 84 | Usage: "path to the extras yaml file", | |
| 85 | Value: "~/.template-extras.yaml", | |
b1ded4b4 2022-10-30 Pavel Safronov | 86 | Destination: &argumentValues.extrasFilePath, |
185f54e8 2022-10-05 Pavel Safronov | 87 | }, |
886f7fa2 2022-11-02 Pavel Safronov | 88 | &cli.StringFlag{ |
| 89 | Name: "log-level", | |
| 90 | Aliases: []string{"l"}, | |
| 91 | Usage: "log level", | |
| 92 | Value: defaultLogLevel, | |
| 93 | Destination: &argumentValues.logLevel, | |
| 94 | }, | |
f819f801 2022-09-21 Pavel Safronov | 95 | }, |
8c15a714 2022-11-04 Pavel Safronov | 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() | |
10676da3 2022-11-06 Pavel Safronov | 140 | fmt.Printf("Profile '%v':\n", settings.profile) |
8c15a714 2022-11-04 Pavel Safronov | 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 | }, | |
f819f801 2022-09-21 Pavel Safronov | 164 | Action: func(context *cli.Context) error { |
8c15a714 2022-11-04 Pavel Safronov | 165 | settings, err := createSettings(argumentValues, context) |
05f9e8ef 2022-10-06 Pavel Safronov | 166 | if err != nil { |
b1ded4b4 2022-10-30 Pavel Safronov | 167 | return fmt.Errorf("error parsing arguments: %w", err) |
05f9e8ef 2022-10-06 Pavel Safronov | 168 | } |
b1ded4b4 2022-10-30 Pavel Safronov | 169 | err = mainLogic(settings) |
| 170 | if err != nil { | |
| 171 | return fmt.Errorf("error in main logic: %w", err) | |
55d96f9c 2022-09-23 Pavel Safronov | 172 | } |
b1ded4b4 2022-10-30 Pavel Safronov | 173 | return nil |
| 174 | }, | |
| 175 | } | |
55d96f9c 2022-09-23 Pavel Safronov | 176 | |
b1ded4b4 2022-10-30 Pavel Safronov | 177 | if err := app.Run(os.Args); err != nil { |
| 178 | log.Fatal(err) | |
| 179 | } | |
| 180 | } | |
319b8b3f 2022-09-22 Pavel Safronov | 181 | |
8c15a714 2022-11-04 Pavel Safronov | 182 | func createSettings( |
b1ded4b4 2022-10-30 Pavel Safronov | 183 | argumentValues ArgumentValues, |
| 184 | context *cli.Context, | |
| 185 | ) (Settings, error) { | |
9573ca02 2022-11-16 Pavel Safronov | 186 | envVarValues, err := readEnvVars() |
| 187 | if err != nil { | |
| 188 | return Settings{}, fmt.Errorf("error getting env var values: %w", err) | |
| 189 | } | |
b1ded4b4 2022-10-30 Pavel Safronov | 190 | settings := Settings{ |
| 191 | ArgumentValues: argumentValues, | |
9573ca02 2022-11-16 Pavel Safronov | 192 | EnvVarValues: envVarValues, |
b1ded4b4 2022-10-30 Pavel Safronov | 193 | } |
319b8b3f 2022-09-22 Pavel Safronov | 194 | |
b1ded4b4 2022-10-30 Pavel Safronov | 195 | // read args |
| 196 | args := context.Args() | |
886f7fa2 2022-11-02 Pavel Safronov | 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") | |
319b8b3f 2022-09-22 Pavel Safronov | 220 | |
b1ded4b4 2022-10-30 Pavel Safronov | 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) | |
4a7759db 2022-09-24 Pavel Safronov | 227 | |
b1ded4b4 2022-10-30 Pavel Safronov | 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) | |
8179bb1c 2022-10-26 Pavel Safronov | 234 | |
ae89a42a 2022-11-02 Pavel Safronov | 235 | // extras strings |
9573ca02 2022-11-16 Pavel Safronov | 236 | _, err = os.Stat(settings.extrasFilePath) |
ae89a42a 2022-11-02 Pavel Safronov | 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 | |
b1ded4b4 2022-10-30 Pavel Safronov | 243 | } |
4a7759db 2022-09-24 Pavel Safronov | 244 | |
8c15a714 2022-11-04 Pavel Safronov | 245 | // get user |
b1ded4b4 2022-10-30 Pavel Safronov | 246 | usr, err := user.Current() |
| 247 | if err != nil { | |
| 248 | log.Fatal(err) | |
| 249 | } | |
8c15a714 2022-11-04 Pavel Safronov | 250 | settings.usr = usr |
4a7361b0 2022-09-21 Pavel Safronov | 251 | |
ae89a42a 2022-11-02 Pavel Safronov | 252 | // get config from default path |
b1ded4b4 2022-10-30 Pavel Safronov | 253 | fullConfigPath := getFullPath(settings.configFilePath, *usr) |
8c15a714 2022-11-04 Pavel Safronov | 254 | settings.fullConfigPath = fullConfigPath |
| 255 | ||
5e424f1b 2022-11-12 Pavel Safronov | 256 | var config Config |
9573ca02 2022-11-16 Pavel Safronov | 257 | if settings.useDefaultConfig { |
5e424f1b 2022-11-12 Pavel Safronov | 258 | config = defaultConfig() |
b1ded4b4 2022-10-30 Pavel Safronov | 259 | } else { |
5e424f1b 2022-11-12 Pavel Safronov | 260 | config, err = loadConfig(fullConfigPath) |
b1ded4b4 2022-10-30 Pavel Safronov | 261 | if err != nil { |
5e424f1b 2022-11-12 Pavel Safronov | 262 | return settings, fmt.Errorf("error loading config from %v: %w", fullConfigPath, err) |
ae89a42a 2022-11-02 Pavel Safronov | 263 | } else { |
5e424f1b 2022-11-12 Pavel Safronov | 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) | |
ae89a42a 2022-11-02 Pavel Safronov | 271 | if err != nil { |
5e424f1b 2022-11-12 Pavel Safronov | 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 | } | |
ae89a42a 2022-11-02 Pavel Safronov | 279 | } |
b1ded4b4 2022-10-30 Pavel Safronov | 280 | } |
| 281 | } | |
8c15a714 2022-11-04 Pavel Safronov | 282 | settings.config = config |
5e424f1b 2022-11-12 Pavel Safronov | 283 | |
| 284 | return settings, nil | |
8c15a714 2022-11-04 Pavel Safronov | 285 | } |
b1ded4b4 2022-10-30 Pavel Safronov | 286 | |
9573ca02 2022-11-16 Pavel Safronov | 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 | ||
8c15a714 2022-11-04 Pavel Safronov | 302 | func findProfile(settings Settings) (Profile, error) { |
| 303 | var profile Profile = Profile{} | |
| 304 | var err error | |
| 305 | config := settings.config | |
ae89a42a 2022-11-02 Pavel Safronov | 306 | if settings.profile == "" { |
| 307 | profile, err = findDefaultProfile(config.Profiles) | |
| 308 | if err != nil { | |
8c15a714 2022-11-04 Pavel Safronov | 309 | return profile, fmt.Errorf("no profile specified and no default profile found") |
ae89a42a 2022-11-02 Pavel Safronov | 310 | } |
| 311 | } else { | |
| 312 | index, err := findProfileIndex(config.Profiles, settings.profile) | |
| 313 | if err != nil { | |
8c15a714 2022-11-04 Pavel Safronov | 314 | return profile, fmt.Errorf("error finding profile: %v", err) |
ae89a42a 2022-11-02 Pavel Safronov | 315 | } |
| 316 | profile = config.Profiles[index] | |
b1ded4b4 2022-10-30 Pavel Safronov | 317 | } |
8c15a714 2022-11-04 Pavel Safronov | 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 | } | |
b1ded4b4 2022-10-30 Pavel Safronov | 327 | |
| 328 | // process profile | |
8c15a714 2022-11-04 Pavel Safronov | 329 | errors := processProfile(profile, settings.projectName, settings.extras, *settings.usr) |
b1ded4b4 2022-10-30 Pavel Safronov | 330 | if len(errors) > 0 { |
| 331 | return fmt.Errorf("error processing profile: %v", errors) | |
| 332 | } | |
| 333 | ||
| 334 | return nil | |
f819f801 2022-09-21 Pavel Safronov | 335 | } |