Skip to content

Commit

Permalink
cache template
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist committed May 6, 2020
1 parent 749ec25 commit cda2465
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 10 deletions.
30 changes: 22 additions & 8 deletions spin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"fmt"
"os"
"path/filepath"

"github.com/egoist/spin/utils"
"gopkg.in/alecthomas/kingpin.v2"
Expand All @@ -13,23 +15,35 @@ func main() {

kingpin.Parse()

if utils.FileExists(*target) {
if utils.PathExists(*target) {
isEmpty, _ := utils.IsEmpty(*target)
if !isEmpty {
fmt.Println("Output directory already exists and is not empty")
return
}
}

url := fmt.Sprintf("https://github.com/%s/archive/master/archive.zip", *repo)
homedir, _ := os.UserHomeDir()
cacheDir := filepath.Join(homedir, fmt.Sprintf(".spin/templates/github/%s", *repo))

outFile, err := utils.DownloadFile(url)
if !utils.PathExists(cacheDir) {
url := fmt.Sprintf("https://github.com/%s/archive/master/archive.zip", *repo)

if err != nil {
zipFile, err := utils.DownloadFile(url)

if err != nil {
fmt.Println("error", err)
return
}

utils.Unzip(zipFile.Name(), cacheDir)
}

if err := utils.Copy(cacheDir, *target); err != nil {
fmt.Println("error", err)
} else {
fmt.Printf("Creating new project in %s\n", *target)
utils.Unzip(outFile.Name(), *target)
fmt.Println("Success!")
return
}

fmt.Printf("Creating new project in %s\n", *target)
fmt.Println("Success!")
}
175 changes: 175 additions & 0 deletions utils/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package utils

import (
"io"
"io/ioutil"
"os"
"path/filepath"
)

const (
// tmpPermissionForDirectory makes the destination directory writable,
// so that stuff can be copied recursively even if any original directory is NOT writable.
// See https://github.com/otiai10/copy/pull/9 for more information.
tmpPermissionForDirectory = os.FileMode(0755)
)

// Copy copies src to dest, doesn't matter if src is a directory or a file.
func Copy(src, dest string, opt ...Options) error {
info, err := os.Lstat(src)
if err != nil {
return err
}
return copy(src, dest, info, assure(opt...))
}

// copy dispatches copy-funcs according to the mode.
// Because this "copy" could be called recursively,
// "info" MUST be given here, NOT nil.
func copy(src, dest string, info os.FileInfo, opt Options) error {

if opt.Skip(src) {
return nil
}

if info.Mode()&os.ModeSymlink != 0 {
return onsymlink(src, dest, info, opt)
}

if info.IsDir() {
return dcopy(src, dest, info, opt)
}
return fcopy(src, dest, info, opt)
}

// fcopy is for just a file,
// with considering existence of parent directory
// and file permission.
func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {

if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
return
}

f, err := os.Create(dest)
if err != nil {
return
}
defer fclose(f, &err)

if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil {
return
}

s, err := os.Open(src)
if err != nil {
return
}
defer fclose(s, &err)

if _, err = io.Copy(f, s); err != nil {
return
}

if opt.Sync {
err = f.Sync()
}

return
}

// dcopy is for a directory,
// with scanning contents inside the directory
// and pass everything to "copy" recursively.
func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {

originalMode := info.Mode()

// Make dest dir with 0755 so that everything writable.
if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil {
return
}
// Recover dir mode with original one.
defer chmod(destdir, originalMode|opt.AddPermission, &err)

contents, err := ioutil.ReadDir(srcdir)
if err != nil {
return
}

for _, content := range contents {
cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
if err = copy(cs, cd, content, opt); err != nil {
// If any error, exit immediately
return
}
}

return
}

func onsymlink(src, dest string, info os.FileInfo, opt Options) error {

switch opt.OnSymlink(src) {
case Shallow:
return lcopy(src, dest)
case Deep:
orig, err := os.Readlink(src)
if err != nil {
return err
}
info, err = os.Lstat(orig)
if err != nil {
return err
}
return copy(orig, dest, info, opt)
case Skip:
fallthrough
default:
return nil // do nothing
}
}

// lcopy is for a symlink,
// with just creating a new symlink by replicating src symlink.
func lcopy(src, dest string) error {
src, err := os.Readlink(src)
if err != nil {
return err
}
return os.Symlink(src, dest)
}

// fclose ANYHOW closes file,
// with asiging error raised during Close,
// BUT respecting the error already reported.
func fclose(f *os.File, reported *error) {
if err := f.Close(); *reported == nil {
*reported = err
}
}

// chmod ANYHOW changes file mode,
// with asiging error raised during Chmod,
// BUT respecting the error already reported.
func chmod(dir string, mode os.FileMode, reported *error) {
if err := os.Chmod(dir, mode); *reported == nil {
*reported = err
}
}

// assure Options struct, should be called only once.
// All optional values MUST NOT BE nil/zero after assured.
func assure(opts ...Options) Options {
if len(opts) == 0 {
return getDefaultOptions()
}
defopt := getDefaultOptions()
if opts[0].OnSymlink == nil {
opts[0].OnSymlink = defopt.OnSymlink
}
if opts[0].Skip == nil {
opts[0].Skip = defopt.Skip
}
return opts[0]
}
46 changes: 46 additions & 0 deletions utils/copy_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package utils

import "os"

// Options specifies optional actions on copying.
type Options struct {
// OnSymlink can specify what to do on symlink
OnSymlink func(src string) SymlinkAction
// Skip can specify which files should be skipped
Skip func(src string) bool
// AddPermission to every entities,
// NO MORE THAN 0777
AddPermission os.FileMode
// Sync file after copy.
// Useful in case when file must be on the disk
// (in case crash happens, for example),
// at the expense of some performance penalty
Sync bool
}

// SymlinkAction represents what to do on symlink.
type SymlinkAction int

const (
// Deep creates hard-copy of contents.
Deep SymlinkAction = iota
// Shallow creates new symlink to the dest of symlink.
Shallow
// Skip does nothing with symlink.
Skip
)

// getDefaultOptions provides default options,
// which would be modified by usage-side.
func getDefaultOptions() Options {
return Options{
OnSymlink: func(string) SymlinkAction {
return Shallow // Do shallow copy
},
Skip: func(string) bool {
return false // Don't skip
},
AddPermission: 0, // Add nothing
Sync: false, // Do not sync
}
}
4 changes: 2 additions & 2 deletions utils/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"os"
)

// FileExists checks if a file exists
func FileExists(name string) bool {
// PathExists checks if a file exists
func PathExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
Expand Down

0 comments on commit cda2465

Please sign in to comment.