From e7e13659388778564907e8844552402ea81704af Mon Sep 17 00:00:00 2001 From: Kevin Offet Date: Sun, 9 Apr 2023 08:19:40 -0400 Subject: [PATCH] pretty close to first fully working version --- .gitignore | 4 + Makefile | 77 ++++++++++++++ data-strings.go | 12 +++ go.mod | 2 + go.sum | 2 + main.go | 261 +++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 data-strings.go create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 02cce98..bd99f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ scaffold .env .env.toml env.toml + +build/ +buildTime.txt +buildVersion.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4e97b9a --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +## Standard Makefile preamble +## according to https://tech.davis-hansson.com/p/make/ + +## Specify a shell +SHELL := bash + +## set shell options +.SHELLFLAGS := -eu -o pipefail -c + +## Makefile defaults +.ONESHELL: +.DELETE_ON_ERROR: + +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rules + +## Modify Makefile parsing +ifeq ($(origin .RECIPEPREFIX), undefined) + $(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later) +endif +.RECIPEPREFIX = > + +#---------------------------------------------------------------- +# Variables +#---------------------------------------------------------------- +progName = scaffold + +#---------------------------------------------------------------- +# Helpers +#---------------------------------------------------------------- + +## help: print this help message + +help: +> @echo 'Usage:' +> @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' + +## confirm: a helper script to check your intent +confirm: +> @echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ] + +## clean: clears temp files +clean: +> @echo 'Cleaning temp files ...' +> @echo '' > buildTime.txt +> @echo '' > buildVersion.txt +> @echo 'Done cleaning.' + +#---------------------------------------------------------------- +# Build +#---------------------------------------------------------------- + +# build strips debugging info from binary +linkerflags = '-s' + +prep: clean +> date +"%F %a %T %Z" > buildTime.txt +> git describe --always --tags --dirty --long > buildVersion.txt + +build: prep +> @go build -o build/$(progName) . && echo "Build success" || echo "[FAILED] go build" + +build-production: prep +> @go build -ldflags=${linkerflags} -o build/$(progName) . && echo "production build success" || echo "[FAILED] production build" + +#------------------------------------------------------------- +# Install - same as build, but places binary on system path +#------------------------------------------------------------- + +install: prep +> @go install . && echo "Build and Install success" || echo "[FAILED] go install" + +install-production: prep +> @go install -ldflags=${linkerflags} . && echo "production build and install success" || echo "[FAILED] production install" + +## Commands that don't relate to a specific file +.PHONY: help confirm clean prep build build-production \ No newline at end of file diff --git a/data-strings.go b/data-strings.go new file mode 100644 index 0000000..0bc94e8 --- /dev/null +++ b/data-strings.go @@ -0,0 +1,12 @@ +package main + +var prog_help = `Scaffold requires 2 parameters +1) a project type +and +2) a name for your new project + +Usage examples: + +scaffold go NewGoProject +scaffold py aPythonProject +` diff --git a/go.mod b/go.mod index a55fc7e..434aea7 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module article12apps.net/scaffold go 1.20 + +require github.com/BurntSushi/toml v1.2.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4614a74 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= diff --git a/main.go b/main.go index 97e0ccc..f1f5f60 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,266 @@ package main import ( - "fmt" + _ "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 + //Sub_Dirs []string + //Def_project_name string + Project_dir_permissions string + //File_permissions string + //Copy_Examples bool + //Copy_Command string + Setup_git bool + Create_Remote_Repo bool + Remote_User string + Remote_Location string + Remote_Shortname string + //Create_gitignore bool + //Git_ignore_content string + //Include_license bool + //License_name string + //Use_license_file bool + //License_filename string + //License_text string + //Use_external_license bool + //External_license_location string + //Use_remote_license bool + //Remote_license_location string + //Main_content string + //Create_readme bool + //Readme_text string + TestKey string +} + +var ( + infoFlag = flag.Bool("Info", false, "Displays the program build information and exits.") + sep = "/" + cfg_dir = ".config/devel/scaffold" + cfg_filename = "scaffold-cfg.toml" + setup Setup + npsetup Setup + //projCfg Setup ) func main() { - fmt.Println("New Project ready") + + flag.Parse() + + if *infoFlag { + fmt.Printf("Newgo 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) + + fmt.Printf("Read config TestKey: %s\n", setup.TestKey) + + pts, err := availableProjectTypes(scaffold_cfg_dir) + ifFerr("Unable to read scaffold config directory contents", err) + //fmt.Printf("Project Types: %+v\n", pts) + + // 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("Read %s project config: %+v\n", projType, npsetup) + + //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 + + //fmt.Println("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) + } + //fmt.Printf("dir: %v,path: %s, name: %s\n", info.IsDir(), path, info.Name()) + 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) + + // setup git + if npsetup.Setup_git { + // init repo + _, err = exec.Command("git", "init").Output() + ifFerr("Unable to initialize git repository", err) + + // 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 + _, 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 }