5 Commits

Author SHA1 Message Date
ec313ab3c3 +gitea bare repo cloning 2023-04-17 05:19:25 -04:00
7680b34aca added linux-release, more readme 2023-04-16 01:35:28 -04:00
5364bfaa8a little improvements and more docs 2023-04-16 00:36:33 -04:00
1d2d293b21 more readme +dirtree pic 2023-04-15 16:06:39 -04:00
aa955c3b4f improved building 2023-04-15 13:33:09 -04:00
7 changed files with 340 additions and 155 deletions

3
.gitignore vendored
View File

@@ -9,5 +9,4 @@ cfg/*.env
cfg/*.toml cfg/*.toml
build/ build/
buildTime.txt build*.txt
buildVersion.txt

View File

@@ -26,6 +26,7 @@ endif
progName = scaffold progName = scaffold
arch = amd64 arch = amd64
winbinName = $(progName).exe winbinName = $(progName).exe
releaseTag = $(shell git describe --abbrev=0)
#---------------------------------------------------------------- #----------------------------------------------------------------
# Helpers # Helpers
@@ -53,9 +54,10 @@ clean:
#---------------------------------------------------------------- #----------------------------------------------------------------
# build strips debugging info from binary # build strips debugging info from binary
linkerflags = '-s' linkerflags = '-s -w'
prep: clean prep: clean
> @mkdir -p build
> date +"%F %a %T %Z" > buildTime.txt > date +"%F %a %T %Z" > buildTime.txt
> git describe --always --tags --dirty --long > buildVersion.txt > git describe --always --tags --dirty --long > buildVersion.txt
@@ -64,16 +66,21 @@ build: prep linux windows
linux: linux:
> @GOOS=$@ GOARCH=$(arch) go build -o build/$@/$(progName) . && echo $@ "Build success" || echo $@ "[FAILED] go build" > @GOOS=$@ GOARCH=$(arch) go build -o build/$@/$(progName) . && echo $@ "Build success" || echo $@ "[FAILED] go build"
linux-release:
> @GOOS=linux GOARCH=$(arch) go build -ldflags=${linkerflags} -o build/linux/$(progName)-linux-$(arch)-$(releaseTag) . && echo $@ "Build success" || echo $@ "[FAILED] go build"
windows: windows:
> @GOOS=$@ GOARCH=$(arch) go build -o build/$@/$(winbinName) . && echo $@ "Build success" || echo $@ "[FAILED] go build" > @GOOS=$@ GOARCH=$(arch) go build -o build/$@/$(progName)-$@-$(arch)-$(releaseTag).exe . && echo $@ "Build success" || echo $@ "[FAILED] go build"
build-production: prep linx-production build-production: prep linx-production
linux-production: linux-production:
> @GOOS=linx GOARCH=$(arch) go build -ldflags=${linkerflags} -o build/$@/$(progName) . && echo "production build success" || echo "[FAILED] production build" > @GOOS=linux GOARCH=$(arch) go build -ldflags=${linkerflags} -o build/$@/$(progName) . && echo "production build success" || echo "[FAILED] production build"
release: prep linux-release windows
#------------------------------------------------------------- #-------------------------------------------------------------
# Install - same as build, but places binary on system path # Install - same as build, but places linux binary on system path
#------------------------------------------------------------- #-------------------------------------------------------------
install: prep install: prep
@@ -83,4 +90,4 @@ install-production: prep
> @go install -ldflags=${linkerflags} . && echo "Production build and install success" || echo "[FAILED] Production install" > @go install -ldflags=${linkerflags} . && echo "Production build and install success" || echo "[FAILED] Production install"
## Commands that don't relate to a specific file ## Commands that don't relate to a specific file
.PHONY: help confirm clean prep build build-production linux windows amd64 .PHONY: help confirm clean prep build build-production linux windows amd64 release

View File

@@ -1,20 +1,73 @@
# scaffold # scaffold
## Intro ## Intro
Use scaffold to setup a new project with a directory skeleton of your design, and if you'd like, it can also automatically initialize local, remote and gitea git repositories for the new project. Use scaffold to setup a new project with a directory skeleton of your design, and if you'd like, it can also automatically initialize local, remote and gitea git repositories for the new project.
## What scaffold does
- A new project directory is created
- Your project skeleton/template is copied from your config/projectType/sample directory
- your setup/initialization commmands are run
- And if you enable them
- a git repository is initialized in the project directory
- a remote repository is created and setup
- a gitea repository is created and setup
## Installation
1. Grab a binary version for your system from the releases page.
2. Put it in a directory that is on your path.
3. Now configure your preferred setups/layouts/skeletons/templates for your projects.
## Configuration
On linux, the configuration directory will be at /home/user/.config/devel/scaffold
For the windows versions, you might want to look at your User_Configuration_Directories location, and change it according to your needs. On windows it's value is the %APPDATA% environment variable.
In a PowerShell, use `Get Child-Item Env:` to display all the environment variables.
Then `[Environment]::SetEnvironmentVariable("APPDATA","C:\Users\You\Wherever-you-like\","User")` to set the new value.
It's a little simpler at a command prompt -- type the command `set` and hit `Enter`. See what `APPDATA` is currently set to. Change it with `setx APPDATA "C:\Users\You\SomeWhere"`.
The examples directory contains an example-scaffold-projectType.toml configuration file. Place a copy in each projectType directory, adjusted to your preferences per the given project type.
## Naming convention warning
Creating repositories on gitea via a ssh push has a side effect -- the project name is forced to lowercase. There are no configuration options to change this. Please see gitea and it's documentation for full explanations.
Here are a couple of thoughts:
1. Adapt and only create projects use lowercase (myspecialproject) or lowersnakecase (my_special_project) or use hyphens between words (my-special-project)
- all lowercase is universally accepted but hard to read for multi-word project names
- lowersnakecase is not universally accepted
- hyphens might not be what you are used to, or prefer, but it seems to be universally accepted and reasonably readable.
2. A work around would be to manually create a reposity with the CamelCase name you want via gitea's web UI. And use scaffold with the -g flag
eg. `scaffold -g MySpecialProject go MySpecialProject` to have scaffold clone it, build it out according to your skeleton/templates, and then push the changes.
## The `scaffold` command
| Command | Description |
| ------- | ----------- |
| scaffold | The program name |
| flags:
-i or -Info | Display the available project types and the build and version information about the program. |
| -g GiteaProjectName | Clone a gitea repository, not create one. Then push the new structure to it. |
| **Examples:** | |
| scaffold *ProjectType* *NewProjectName* | Stuff |
## Configuration of samples
![Example Directory Structure](assets/images/dirtree-pic.png)
The image should explain alot quickly. The go, go-cli, go-web, hs, js, py and svelte directories define the project types available to scaffold. Arrange the contents of the sample directory, within each project type directory, to your preferred layout for each language/use-case.
## Git ## Git
Git is available to be used as the version control system for new projects. Select/Deselect within the `scaffold-<PROJECTTYPE>.toml` file. Git is available to be used as the version control system for new projects. Select/Deselect within the `scaffold-<PROJECTTYPE>.toml` file.
***Please Note** that if you also use it for your configuration files and sample directories, then a .gitignore file within your sample directory will interfere with git's ability to track your skeletons/templates properly. So, to allow for this, put what you normally would in a .gitignore file into a GITIGNORE file in your sample directory. And scaffold will convert it to a .gitignore file within your newProject directory. *Please Note* that if you also use git to track your configuration files and sample directories, then a `.gitignore` file within your sample directory will interfere with git's ability to track your skeletons/templates properly. So, to allow for this, put what you normally would in a `.gitignore` file instead into a file named `GITIGNORE` in your sample directory. And scaffold will convert it to a `.gitignore` file within your newProject directory.
If you are NOT using git to track your project types and samples/skeletons/templates, then no problem, just place a .gitignore file within the sample directory as you would any other file. If you are NOT using git to track your project types and samples/skeletons/templates, then no problem, just place a .gitignore file within the sample directory as you would any other file.
## The Process
- The new project directory is created
- Your project skeleton is copied from your sample directory
- your setup/initialization commmands are run
- a git repository is initialized in the project directory
- and if you enable it, a remote repository is created and setup

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -17,4 +17,13 @@ var cfg_content = `# Scaffold program configuration
# Too funny - this config file doesn't seem to be needed ;-) # Too funny - this config file doesn't seem to be needed ;-)
# let's keep it for future use -- very silly # let's keep it for future use -- very silly
testkey = "test value"` testkey = "test value"
# Turn on/off testing for the existence of the ssh executable
# it's just an extra safe guard.
# Because it's required for the remote repo and gitea setups.
# If you turn it off, you can still
# control the remote repo and gitea setups
# via the create_remote_repo and setup_gitea options in scaffold-projectType.toml
test_for_ssh = true
`

194
main.go
View File

@@ -32,6 +32,7 @@ type (
Remote_User string Remote_User string
Remote_Location string Remote_Location string
Remote_Label string Remote_Label string
Remote_Alt_Label string
Commands [][]string Commands [][]string
Setup_gitea bool Setup_gitea bool
Gitea_server_account string // should be in ~/.ssh/config Gitea_server_account string // should be in ~/.ssh/config
@@ -41,43 +42,55 @@ type (
} }
ScaffoldSetup struct { ScaffoldSetup struct {
TestKey string TestKey string
TestForSSH bool
} }
) )
var ( var (
infoFlag = flag.Bool("Info", false, "Displays the program build information and exits.") infoFlag = flag.Bool("i", false, "Displays the program build information and exits.")
infoFlag2 = flag.Bool("i", false, "Displays the program build information and exits.") cloneGiteaFlag = flag.Bool("c", false, "Clone from gitea. scaffold expects it to be an empty repository")
giteaRepoFlag = flag.String("g", "", "Specifies a gitea repository to clone from rather than create")
sep = "/" sep = "/"
cfg_dir = ".config/devel/scaffold" cfg_dir = "devel/scaffold" // relative to <USER_CONFIG_DIR>, on linux /home/user/.config
cfg_filename = "scaffold-cfg.toml" cfg_filename = "scaffold-cfg.toml"
setup ScaffoldSetup setup ScaffoldSetup
npsetup Setup npsetup Setup
hasSSH = true
projType string
newProjectName string
) )
func main() { func main() {
flag.Parse() flag.Parse()
if *infoFlag || *infoFlag2 { user_cfg_dir, err := os.UserConfigDir()
ifFerr("Unable to determine user config directory", err)
if *infoFlag {
fmt.Printf("Scaffold version: %s", buildVersion) fmt.Printf("Scaffold version: %s", buildVersion)
fmt.Printf("Built: %s\n", buildTime) fmt.Printf("Built: %s\n", buildTime)
if runtime.GOOS == "windows" { fmt.Printf("Running on a %s system.\n", runtime.GOOS)
fmt.Println("It's a windows system") fmt.Printf("Arg count: %+v\n", os.Args)
} _ = isSSHAvailable()
ucfgd, err := os.UserConfigDir() fmt.Printf("Gitea repo: %s\n", *giteaRepoFlag)
ifFerr("Unable to determine user config directory", err)
fmt.Println("System says -- user config directory: ", ucfgd) at, err := availableProjectTypes(user_cfg_dir + sep + cfg_dir)
ifFerr("Unable to get available project types", err)
fmt.Printf("Avaiable types: %v\n", at)
fmt.Println("System says -- user config directory: ", user_cfg_dir)
os.Exit(0) os.Exit(0)
} }
// Get setup // Setup scaffold
u, err := user.Current() u, err := user.Current()
ifFerr("Unable to get current user details", err) ifFerr("Unable to get current user details", err)
homeDir := u.HomeDir homeDir := u.HomeDir
scaffold_cfg_dir := homeDir + sep + cfg_dir scaffold_cfg_dir := user_cfg_dir + sep + cfg_dir
cfg_file := scaffold_cfg_dir + sep + cfg_filename cfg_file := scaffold_cfg_dir + sep + cfg_filename
if _, err := os.Stat(scaffold_cfg_dir); err != nil { if _, err := os.Stat(scaffold_cfg_dir); err != nil {
@@ -103,19 +116,34 @@ func main() {
ifFerr("[Failed] to parse config file", err) ifFerr("[Failed] to parse config file", err)
} }
if setup.TestForSSH {
hasSSH = isSSHAvailable()
}
pts, err := availableProjectTypes(scaffold_cfg_dir) pts, err := availableProjectTypes(scaffold_cfg_dir)
ifFerr("Unable to read scaffold config directory contents", err) ifFerr("Unable to read scaffold config directory contents", err)
// Handle request // check command line
pc := len(os.Args) // two forms of command
if pc != 3 { if *cloneGiteaFlag {
fmt.Println(prog_help) if len(os.Args) != 4 {
fmt.Printf("Available project types: %v\n", pts) leave(prog_help, pts)
os.Exit(1) }
projType = os.Args[2]
newProjectName = os.Args[3]
fmt.Printf("Not creating ... cloning %s project %s\n", projType, newProjectName)
} else {
if len(os.Args) != 3 {
leave(prog_help, pts)
}
projType = os.Args[1]
newProjectName = os.Args[2]
} }
projType := os.Args[1] if !knownType(projType, pts) {
newProjectName := os.Args[2] log.Fatalf("Unkown project type: %s\n", projType)
os.Exit(1)
}
has := isConfigAvailable(pts, projType) has := isConfigAvailable(pts, projType)
if !has { if !has {
@@ -130,8 +158,6 @@ func main() {
_, err = toml.DecodeFile(projectCfgFile, &npsetup) _, err = toml.DecodeFile(projectCfgFile, &npsetup)
ifFerr("Unable to read configuration for requested project type", err) ifFerr("Unable to read configuration for requested project type", err)
//fmt.Printf("Found commands: %+v\n", npsetup.Commands)
//Setup and Ready to begin //Setup and Ready to begin
// Permissions are octal (ugo - user, group, other) // Permissions are octal (ugo - user, group, other)
@@ -142,6 +168,24 @@ func main() {
// make new project directory // make new project directory
newProjDir := homeDir + sep + npsetup.Projects_basedir + sep + newProjectName newProjDir := homeDir + sep + npsetup.Projects_basedir + sep + newProjectName
// Clone or create ??
if *cloneGiteaFlag {
bd := homeDir + sep + npsetup.Projects_basedir
_, err = exec.Command("mkdir", "-p", bd).Output()
ifFerr("Unable to create projects base directory", err)
fmt.Println("Made or found projects base directory")
err = os.Chdir(bd)
ifFerr("Unable to change into projects basedir: "+bd, err)
loc := "ssh://" + npsetup.Gitea_ssh_host_label + sep + npsetup.Gitea_user_account + sep + newProjectName + ".git"
_, err = exec.Command("git", "clone", loc).Output()
ifFerr("Unable to clone gitea repository at: "+loc, err)
fmt.Printf("Cloned repository from %s\n", loc)
} else {
if _, err = os.Stat(newProjDir); os.IsExist(err) { if _, err = os.Stat(newProjDir); os.IsExist(err) {
log.Fatalf("Project directory already exists -- %s\n", err) log.Fatalf("Project directory already exists -- %s\n", err)
} }
@@ -149,12 +193,12 @@ func main() {
err = os.MkdirAll(newProjDir, os.FileMode(inval)) err = os.MkdirAll(newProjDir, os.FileMode(inval))
ifFerr("Unable to create new project directory ", err) ifFerr("Unable to create new project directory ", err)
fmt.Printf("created new project directory: %s\n", newProjDir) fmt.Printf("created new project directory: %s\n", newProjDir)
}
err = os.Chdir(newProjDir) err = os.Chdir(newProjDir)
ifFerr("Unable to change into new project directory to continue", err) ifFerr("Unable to change into new project directory to continue", err)
// copy contents from projectType/sample // copy contents from projectType/sample
err = filepath.Walk(projectTypeDir+sep+"sample", func(path string, info os.FileInfo, err error) error { err = filepath.Walk(projectTypeDir+sep+"sample", func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@@ -200,11 +244,20 @@ func main() {
fmt.Printf("executing: %+v %+v\n", comm[0], args) fmt.Printf("executing: %+v %+v\n", comm[0], args)
} }
// setup git // setup git and gitea
if npsetup.Setup_git { if hasSSH && npsetup.Setup_git {
setupGit(newProjectName)
}
fmt.Printf("New %s Project: %s ready\n", projType, newProjectName)
}
func setupGit(anpn string) {
if !*cloneGiteaFlag {
// init repo // init repo
_, err = exec.Command("git", "init").Output() _, err := exec.Command("git", "init").Output()
ifFerr("Unable to initialize git repository", err) ifFerr("Unable to initialize git repository", err)
}
nifd, err := os.Create(".gitignore") nifd, err := os.Create(".gitignore")
ifFerr("Unable to create .gitignore file", err) ifFerr("Unable to create .gitignore file", err)
@@ -219,7 +272,7 @@ func main() {
if err != nil { if err != nil {
fmt.Println("Unable to read GITIGNORE, using default content") fmt.Println("Unable to read GITIGNORE, using default content")
} }
gi_content = fmt.Sprintf("%s\n\n%s", newProjectName, string(gi_in)) gi_content = fmt.Sprintf("%s\n\n%s", anpn, string(gi_in))
_, err = nifd.WriteString(gi_content) _, err = nifd.WriteString(gi_content)
ifFerr("Unable to write .gitignore content", err) ifFerr("Unable to write .gitignore content", err)
@@ -244,78 +297,69 @@ func main() {
if npsetup.Create_Remote_Repo { if npsetup.Create_Remote_Repo {
// create remote repo // create remote repo
acomm := fmt.Sprintf("cd %s && git init --bare %s.git", npsetup.Remote_Location, newProjectName) acomm := fmt.Sprintf("cd %s && git init --bare %s.git", npsetup.Remote_Location, anpn)
_, err = exec.Command("ssh", npsetup.Remote_User, acomm).Output() _, err = exec.Command("ssh", npsetup.Remote_User, acomm).Output()
ifFerr("Unable to create remote repository", err) ifFerr("Unable to create remote repository", err)
fmt.Println("Remote repository created") fmt.Println("Remote repository created")
// add remote to local repo // add remote to local repo
new_remote_repo := npsetup.Remote_User + ":" + npsetup.Remote_Location + "/" + newProjectName + ".git" new_remote_repo := npsetup.Remote_User + ":" + npsetup.Remote_Location + "/" + anpn + ".git"
_, err = exec.Command("git", "remote", "add", npsetup.Remote_Label, new_remote_repo).Output() var rlabel string
if *cloneGiteaFlag {
rlabel = npsetup.Remote_Alt_Label
} else {
rlabel = npsetup.Remote_Label
}
_, err = exec.Command("git", "remote", "add", rlabel, new_remote_repo).Output()
ifFerr("Unable to set git remote for new repo", err) ifFerr("Unable to set git remote for new repo", err)
fmt.Println("Added remote repo to git as origin") fmt.Println("Added remote repo to git as " + rlabel)
// push initial commit // push initial commit
// be sure you have set // be sure you have set
// git config --global init.defaultBranch main // git config --global init.defaultBranch main
_, err = exec.Command("git", "push", "origin", "main").Output() _, err = exec.Command("git", "push", rlabel, "main").Output()
ifFerr("Unable to push initial commit to origin", err) ifFerr("Unable to push initial commit to "+rlabel, err)
fmt.Println("Pushed initial commit to remote") fmt.Println("Pushed initial commit to remote")
} }
if npsetup.Setup_gitea { if npsetup.Setup_gitea {
setupGitea(anpn, *cloneGiteaFlag)
}
}
func setupGitea(npn string, cloned bool) {
if !cloned {
// create gitea repo // create gitea repo
grcomm := fmt.Sprintf("%s:%s/%s.git", npsetup.Gitea_ssh_host_label, npsetup.Gitea_user_account, newProjectName) grcomm := fmt.Sprintf("%s:%s/%s.git", npsetup.Gitea_ssh_host_label, npsetup.Gitea_user_account, npn)
_, err = exec.Command("git", "remote", "add", npsetup.Remote_Label_gitea, grcomm).Output() _, err := exec.Command("git", "remote", "add", npsetup.Remote_Label_gitea, grcomm).Output()
ifFerr("Unable to set git remote for new project to gitea", err) 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) fmt.Println("Added remote repo to git for gitea as ", npsetup.Remote_Label_gitea)
}
var rlabel string
if *cloneGiteaFlag {
rlabel = "origin"
} else {
rlabel = npsetup.Remote_Label_gitea
}
// -u sets upstream tracking // -u sets upstream tracking
_, err = exec.Command("git", "push", "-u", npsetup.Remote_Label_gitea, "main").Output() _, err := exec.Command("git", "push", "-u", rlabel, "main").Output()
ifFerr("Unable to push new project to gitea", err) ifFerr("Unable to push new project to gitea", err)
fmt.Println("Pushed new project to gitea") fmt.Println("Pushed new project to gitea")
} }
func leave(ph string, ptypes []string) {
fmt.Println(ph)
fmt.Printf("Available project types: %v\n", ptypes)
os.Exit(1)
} }
fmt.Printf("New %s Project: %s ready\n", projType, newProjectName) func knownType(at string, ptypes []string) bool {
} for _, v := range ptypes {
if v == at {
// if Fatal err return true
func ifFerr(msg string, err error) {
if err != nil {
log.Fatalf("%s -- %s\n", msg, err)
} }
} }
return false
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
} }

73
support_functions.go Normal file
View File

@@ -0,0 +1,73 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"runtime"
)
// 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() {
if e.Name() == ".git" {
continue
}
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
}
func isSSHAvailable() bool {
//rv := false
//var res string
var resb, resb2 []byte
var err error
system := runtime.GOOS
switch system {
case "linux":
resb, err = exec.Command("which", "ssh").Output()
ifFerr("Unable to test for ssh on linux", err)
resb2, _ = exec.Command("which", "ssh3").Output()
case "window":
resb, err = exec.Command("where", "ssh").Output()
ifFerr("Unable to test for ssh on windows", err)
resb2, _ = exec.Command("which", "ssh3").Output()
}
fmt.Println("ssh is at: ", string(resb))
fmt.Println("ssh - len resb: ", len(resb))
fmt.Println("ssh3 - len resb2: ", len(resb2))
return len(resb) != 0
}