From c9bd2f9168f3aa70155fc8ebef3156f6df42a552 Mon Sep 17 00:00:00 2001 From: Tamer Tas <contact@tmrts.com> Date: Sat, 19 Dec 2015 12:47:24 +0200 Subject: [PATCH] Create metadata for templates --- pkg/cmd/download.go | 8 ++++-- pkg/cmd/metadata.go | 30 +++++++++++++++++++++ pkg/cmd/save.go | 5 ++++ pkg/template/metadata.go | 51 +++++++++++++++++++++++++++++++++++ pkg/template/metadata_test.go | 27 +++++++++++++++++++ pkg/template/template.go | 46 ++++++++++++++++++++++++++----- pkg/tmplt/configuration.go | 17 ++++++++++-- 7 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 pkg/cmd/metadata.go create mode 100644 pkg/template/metadata.go create mode 100644 pkg/template/metadata_test.go diff --git a/pkg/cmd/download.go b/pkg/cmd/download.go index 2e3d536..35df834 100644 --- a/pkg/cmd/download.go +++ b/pkg/cmd/download.go @@ -59,7 +59,7 @@ func downloadZip(URL, targetDir string) error { defer rc.Close() } - // split first token of f.Name since it's zip file name + // splits the first token of f.Name since it's zip file name path := filepath.Join(dest, strings.SplitAfterN(f.Name, "/", 2)[1]) if f.FileInfo().IsDir() { @@ -89,6 +89,7 @@ func downloadZip(URL, targetDir string) error { } } + // TODO Wrap this function in a validation wrapper from top to bottom if _, err := util.ValidateTemplate(targetDir); err != nil { return err } @@ -134,7 +135,6 @@ var Download = &cli.Command{ zipURL := host.ZipURL(templateURL) - // TODO validate template as well if err := downloadZip(zipURL, targetDir); err != nil { // Delete if download transaction fails defer os.RemoveAll(targetDir) @@ -142,6 +142,10 @@ var Download = &cli.Command{ exit.Error(fmt.Errorf("download: %s", err)) } + if err := serializeMetadata(templateName, templateURL, targetDir); err != nil { + exit.Error(fmt.Errorf("download: %s", err)) + } + exit.OK("Successfully downloaded the template %v", templateName) }, } diff --git a/pkg/cmd/metadata.go b/pkg/cmd/metadata.go new file mode 100644 index 0000000..df06177 --- /dev/null +++ b/pkg/cmd/metadata.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/tmrts/tmplt/pkg/template" + "github.com/tmrts/tmplt/pkg/tmplt" +) + +func serializeMetadata(tag string, repo string, targetDir string) error { + fname := filepath.Join(targetDir, tmplt.TemplateMetadataName) + + f, err := os.Create(fname) + if err != nil { + return err + } else { + defer f.Close() + } + + enc := json.NewEncoder(f) + + t := template.Metadata{tag, repo, template.NewTime()} + if err := enc.Encode(&t); err != nil { + return err + } + + return nil +} diff --git a/pkg/cmd/save.go b/pkg/cmd/save.go index bcdeafe..4e70719 100644 --- a/pkg/cmd/save.go +++ b/pkg/cmd/save.go @@ -15,6 +15,7 @@ import ( ) var Save = &cli.Command{ + // TODO rename template-name to template-tag Use: "save <template-path> <template-name>", Short: "Save a project template to local template registry", Run: func(c *cli.Command, args []string) { @@ -53,6 +54,10 @@ var Save = &cli.Command{ exit.Error(err) } + if err := serializeMetadata(templateName, "local:"+tmplDir, targetDir); err != nil { + exit.Error(fmt.Errorf("save: %s", err)) + } + exit.OK("Successfully saved the template %v", templateName) }, } diff --git a/pkg/template/metadata.go b/pkg/template/metadata.go new file mode 100644 index 0000000..97ad507 --- /dev/null +++ b/pkg/template/metadata.go @@ -0,0 +1,51 @@ +package template + +import ( + "fmt" + "time" + + "github.com/docker/go-units" +) + +type Metadata struct { + Tag string + Repository string + + Created JSONTime +} + +func (m Metadata) String() []string { + tDelta := time.Now().Sub(time.Time(m.Created)) + return []string{m.Tag, m.Repository, units.HumanDuration(tDelta) + " ago"} +} + +type JSONTime time.Time + +const ( + timeFormat = "Mon Jan 2 15:04 -0700 MST 2006" +) + +func NewTime() JSONTime { + return JSONTime(time.Now()) +} + +func (t *JSONTime) MarshalJSON() ([]byte, error) { + stamp := fmt.Sprintf(`"%s"`, time.Time(*t).Format(timeFormat)) + + return []byte(stamp), nil +} + +func (t *JSONTime) UnmarshalJSON(b []byte) error { + time, err := time.Parse(timeFormat, string(b)[1:len(b)-1]) + if err != nil { + return err + } + + *t = JSONTime(time) + + return nil +} + +func (t JSONTime) String() string { + return fmt.Sprintf("%s", time.Time(t).Format(timeFormat)) +} diff --git a/pkg/template/metadata_test.go b/pkg/template/metadata_test.go new file mode 100644 index 0000000..6f2e0e1 --- /dev/null +++ b/pkg/template/metadata_test.go @@ -0,0 +1,27 @@ +package template_test + +import ( + "encoding/json" + "testing" + + "github.com/tmrts/tmplt/pkg/template" +) + +func TestMarshalsTime(t *testing.T) { + jsonT := template.NewTime() + + b, err := jsonT.MarshalJSON() + if err != nil { + t.Error(err) + } + + var unmarshaledT template.JSONTime + if err := json.Unmarshal(b, &unmarshaledT); err != nil { + t.Error(err) + } + + expected, got := jsonT.String(), unmarshaledT.String() + if expected != got { + t.Errorf("marshaled and unmarshaled time should've been equal expected %q, got %q", expected, got) + } +} diff --git a/pkg/template/template.go b/pkg/template/template.go index f482e91..14494a3 100644 --- a/pkg/template/template.go +++ b/pkg/template/template.go @@ -9,12 +9,18 @@ import ( "github.com/tmrts/tmplt/pkg/prompt" "github.com/tmrts/tmplt/pkg/tmplt" + "github.com/tmrts/tmplt/pkg/util/osutil" "github.com/tmrts/tmplt/pkg/util/stringutil" ) type Interface interface { Execute(string) error UseDefaultValues() + Info() Metadata +} + +func (t dirTemplate) Info() Metadata { + return t.Metadata } func Get(path string) (Interface, error) { @@ -49,18 +55,44 @@ func Get(path string) (Interface, error) { return metadata, nil }(filepath.Join(absPath, tmplt.ContextFileName)) + metadataExists, err := osutil.FileExists(filepath.Join(absPath, tmplt.TemplateMetadataName)) + if err != nil { + return nil, err + } + + md, err := func() (Metadata, error) { + if !metadataExists { + return Metadata{}, nil + } + + b, err := ioutil.ReadFile(filepath.Join(absPath, tmplt.TemplateMetadataName)) + if err != nil { + return Metadata{}, err + } + + var m Metadata + if err := json.Unmarshal(b, &m); err != nil { + return Metadata{}, err + } + + return m, nil + }() + return &dirTemplate{ - Context: ctxt, - FuncMap: FuncMap, - Path: filepath.Join(absPath, tmplt.TemplateDirName), + Context: ctxt, + FuncMap: FuncMap, + Path: filepath.Join(absPath, tmplt.TemplateDirName), + Metadata: md, }, err } type dirTemplate struct { - Path string - Context map[string]interface{} - FuncMap template.FuncMap + Path string + Context map[string]interface{} + FuncMap template.FuncMap + Metadata Metadata + alignment string ShouldUseDefaults bool } @@ -73,6 +105,7 @@ func (t *dirTemplate) BindPrompts() { for s, v := range t.Context { t.FuncMap[s] = func() interface{} { switch v := v.(type) { + // First is the default value if it's a slice case []interface{}: return v[0] } @@ -93,6 +126,7 @@ func (t *dirTemplate) Execute(dirPrefix string) error { // TODO create io.ReadWriter from string // TODO refactor name manipulation + // TODO trim leading or trailing whitespaces return filepath.Walk(t.Path, func(filename string, info os.FileInfo, err error) error { if err != nil { return err diff --git a/pkg/tmplt/configuration.go b/pkg/tmplt/configuration.go index bd8ef29..c96b19a 100644 --- a/pkg/tmplt/configuration.go +++ b/pkg/tmplt/configuration.go @@ -9,6 +9,7 @@ import ( "github.com/tmrts/tmplt/pkg/util/exit" "github.com/tmrts/tmplt/pkg/util/osutil" + "github.com/tmrts/tmplt/pkg/util/tlog" ) const ( @@ -19,8 +20,9 @@ const ( ConfigFileName = "config.json" TemplateDir = "templates" - ContextFileName = "project.json" - TemplateDirName = "template" + ContextFileName = "project.json" + TemplateDirName = "template" + TemplateMetadataName = "__metadata.json" GithubOwner = "tmrts" GithubRepo = "tmplt" @@ -45,6 +47,17 @@ func init() { Configuration.FilePath = filepath.Join(homeDir, ConfigDirPath, ConfigFileName) Configuration.TemplateDirPath = filepath.Join(homeDir, ConfigDirPath, TemplateDir) + IsTemplateDirInitialized, err := osutil.DirExists(Configuration.TemplateDirPath) + if err != nil { + exit.Error(err) + } + + // TODO perform this in related commands only with ValidateInitialization + if !IsTemplateDirInitialized { + tlog.Warn("Template registry is not initialized. Please run `init` command to create it.") + return + } + // Read .config/tmplt/config.json if exists // TODO use defaults if config.json doesn't exist hasConfig, err := osutil.FileExists(Configuration.FilePath) -- GitLab