package main import ( _ "embed" "flag" "fmt" "io" "log" "os" "os/exec" "os/user" "path/filepath" "strconv" "github.com/BurntSushi/toml" ) //go:embed buildTime.txt var buildTime string //go:embed buildVersion.txt var buildVersion string type ( Setup struct { Projects_basedir string Module_basepath string Project_dir_permissions string Setup_git bool Create_Remote_Repo bool Remote_User string Remote_Location string Remote_Shortname string } ScaffoldSetup struct { TestKey string } ) var ( infoFlag = flag.Bool("Info", false, "Displays the program build information and exits.") infoFlag2 = flag.Bool("i", false, "Displays the program build information and exits.") sep = "/" cfg_dir = ".config/devel/scaffold" cfg_filename = "scaffold-cfg.toml" setup ScaffoldSetup npsetup Setup ) func main() { flag.Parse() if *infoFlag || *infoFlag2 { fmt.Printf("Scaffold version: %s", buildVersion) fmt.Printf("Built: %s\n", buildTime) os.Exit(0) } // Get setup u, err := user.Current() ifFerr("Unable to get current user details", err) homeDir := u.HomeDir scaffold_cfg_dir := homeDir + sep + cfg_dir cfg_file := scaffold_cfg_dir + sep + cfg_filename if _, err := os.Stat(scaffold_cfg_dir); err != nil { e2 := os.MkdirAll(scaffold_cfg_dir, 0700) ifFerr("Unable to create scaffold configuration directory", e2) } if _, err := os.Stat(cfg_file); err != nil { fmt.Println("No scaffold program configuration file found - creating a default file") // ok now do it } else { fmt.Println("found config file") } _, err = toml.DecodeFile(cfg_file, &setup) ifFerr("[Failed] to parse config file", err) pts, err := availableProjectTypes(scaffold_cfg_dir) ifFerr("Unable to read scaffold config directory contents", err) // Handle request pc := len(os.Args) if pc != 3 { fmt.Println(prog_help) fmt.Printf("Available project types: %v\n", pts) os.Exit(1) } projType := os.Args[1] newProjectName := os.Args[2] has := isConfigAvailable(pts, projType) if !has { fmt.Printf("No configuration found for project type: %s\n", projType) os.Exit(1) } fmt.Printf("Project type found: %t\n", has) projectTypeDir := scaffold_cfg_dir + sep + projType projectCfgFile := projectTypeDir + sep + "scaffold-" + projType + ".toml" _, err = toml.DecodeFile(projectCfgFile, &npsetup) ifFerr("Unable to read configuration for requested project type", err) //Setup and Ready to begin // Permissions are octal (ugo - user, group, other) inval, err := strconv.ParseUint(npsetup.Project_dir_permissions, 8, 64) if err != nil { inval = 0700 } // make new project directory newProjDir := homeDir + sep + npsetup.Projects_basedir + sep + newProjectName if _, err = os.Stat(newProjDir); os.IsExist(err) { log.Fatalf("Project directory already exists -- %s\n", err) } err = os.MkdirAll(newProjDir, os.FileMode(inval)) ifFerr("Unable to create new project directory ", err) fmt.Printf("created new project directory: %s\n", newProjDir) err = os.Chdir(newProjDir) ifFerr("Unable to change into new project directory to continue", err) // copy contents from projectType/sample err = filepath.Walk(projectTypeDir+sep+"sample", func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() && path == scaffold_cfg_dir { return nil } if info.IsDir() { err = os.MkdirAll(info.Name(), os.FileMode(inval)) ifFerr("Unable to create sub dir "+info.Name(), err) } else { src, err := os.Open(path) ifFerr("Unable to open file for copying: "+path, err) defer src.Close() dest, err := os.Create(info.Name()) ifFerr("Unable to make new file: "+info.Name(), err) defer dest.Close() _, err = io.Copy(dest, src) ifFerr("Unable to copy file: "+info.Name(), err) } return nil }) if err != nil { fmt.Println(err) } fmt.Println("Project sample skeleton copied") // run init commands // make mod file modpath := npsetup.Module_basepath + sep + newProjectName _, err = exec.Command("go", "mod", "init", modpath).Output() ifFerr("go mod init failed", err) // do a mod tidy to update the mod file _, err = exec.Command("go", "mod", "tidy").Output() ifFerr("Unable to update the mod file with a tidy", err) fmt.Println("Updated go.mod") // setup git if npsetup.Setup_git { // init repo _, err = exec.Command("git", "init").Output() ifFerr("Unable to initialize git repository", err) nifd, err := os.Create(".gitignore") ifFerr("Unable to create .gitignore file", err) defer nifd.Close() gi_content := fmt.Sprintf(gi_template, newProjectName) _, err = nifd.WriteString(gi_content) ifFerr("Unable to write .gitignore content", err) fmt.Println("Wrote .gitignore content") // stage files _, err = exec.Command("git", "add", ".").Output() ifFerr("Unable to stage files prior to first commit", err) // first commit _, err = exec.Command("git", "commit", "-m", "\"Initial Commit\"").Output() ifFerr("Unable to make first commit, are your git config --global user.email and user.name set?", err) fmt.Println("Made initial commit to local repository") if npsetup.Create_Remote_Repo { // create remote repo acomm := fmt.Sprintf("cd %s && git init --bare %s.git", npsetup.Remote_Location, newProjectName) _, err = exec.Command("ssh", npsetup.Remote_User, acomm).Output() ifFerr("Unable to create remote repository", err) fmt.Println("Remote repository created") // add remote to local repo new_remote_repo := npsetup.Remote_User + ":" + npsetup.Remote_Location + "/" + newProjectName + ".git" _, err = exec.Command("git", "remote", "add", npsetup.Remote_Shortname, new_remote_repo).Output() ifFerr("Unable to set git remote for new repo", err) fmt.Println("Added remote repo to git as origin") // push initial commit // be sure you have set // git config --global init.defaultBranch main _, err = exec.Command("git", "push", "origin", "main").Output() ifFerr("Unable to push initial commit to origin", err) fmt.Println("Pushed initial commit to remote") } } fmt.Printf("New %s Project: %s ready\n", projType, newProjectName) } // if Fatal err func ifFerr(msg string, err error) { if err != nil { log.Fatalf("%s -- %s\n", msg, err) } } func availableProjectTypes(adir string) ([]string, error) { var res []string sf, err := os.Open(adir) if err != nil { return nil, err } entries, err := sf.ReadDir(0) if err != nil { return nil, err } for _, e := range entries { if e.IsDir() { res = append(res, e.Name()) } } return res, nil } func isConfigAvailable(types []string, aPType string) bool { res := false for _, v := range types { if v == aPType { res = true break } } return res }