package main import ( _ "embed" "flag" "fmt" "io" "log" "os" "os/exec" "os/user" "path/filepath" "runtime" "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_Label string Commands [][]string Setup_gitea bool Gitea_server_account string // should be in ~/.ssh/config Gitea_ssh_host_label string Gitea_user_account string Remote_Label_gitea 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) if runtime.GOOS == "windows" { fmt.Println("It's a windows system") } ucfgd, err := os.UserConfigDir() ifFerr("Unable to determine user config directory", err) fmt.Println("System says -- user config directory: ", ucfgd) 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") cfgfd, err := os.Create(cfg_file) ifFerr("Unable to create scaffold configuration file", err) defer cfgfd.Close() _, err = cfgfd.WriteString(cfg_content) ifFerr("Unable to write scaffold configuration file content", err) fmt.Println("Wrote new scaffold configuration file") _, err = toml.Decode(cfg_content, &setup) ifFerr("[Failed] to parse config file", err) } 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) //fmt.Printf("Found commands: %+v\n", npsetup.Commands) //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 your init commands from the projectType/scaffold-.toml file for _, comm := range npsetup.Commands { args := comm[1:] if comm[0] == "go" && comm[1] == "mod" && comm[2] == "init" { args[2] = args[2] + sep + newProjectName //fmt.Printf("executing: %+v -- %+v\n", comm, args) } _, err = exec.Command(comm[0], args...).Output() if err != nil { commstring := fmt.Sprintf("Unable to execute command: %+v", comm) log.Fatalf("%s -- %s\n", commstring, err) } fmt.Printf("executing: %+v %+v\n", comm[0], args) } // 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() var gi_content string var gi_in []byte = []byte("bin/") gisrc, err := os.Open("GITIGNORE") if err == nil { // assume error means there is no source file gi_in, err = io.ReadAll(gisrc) if err != nil { fmt.Println("Unable to read GITIGNORE, using default content") } gi_content = fmt.Sprintf("%s\n\n%s", newProjectName, string(gi_in)) _, err = nifd.WriteString(gi_content) ifFerr("Unable to write .gitignore content", err) fmt.Println("Wrote .gitignore content") // remove GITIGNORE err = os.Remove("GITIGNORE") if err != nil { fmt.Println("Unable to remove GITIGNORE file") } } defer gisrc.Close() // 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_Label, 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") } if npsetup.Setup_gitea { // create gitea repo grcomm := fmt.Sprintf("%s:%s/%s.git", npsetup.Gitea_ssh_host_label, npsetup.Gitea_user_account, newProjectName) _, err = exec.Command("git", "remote", "add", npsetup.Remote_Label_gitea, grcomm).Output() ifFerr("Unable to set git remote for new project to gitea", err) fmt.Println("Added remote repo to git for gitea as ", npsetup.Remote_Label_gitea) // -u sets upstream tracking _, err = exec.Command("git", "push", "-u", npsetup.Remote_Label_gitea, "main").Output() ifFerr("Unable to push new project to gitea", err) fmt.Println("Pushed new project to gitea") } } 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 }