Skip to content

Commit

Permalink
feat: add command in charmil cli (#207)
Browse files Browse the repository at this point in the history
* feat: add command in charmil cli

* feat: add command in charmil

* fix: build issues

* fix: typo in logging

* chore: apply review suggestions
  • Loading branch information
ankithans authored Aug 9, 2021
1 parent 34b27bb commit f706d09
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 45 deletions.
25 changes: 25 additions & 0 deletions cli/cmd/charmil/locales/en/add.en.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
add.cmd.use:
description: "Use of add command"
one: "add"
add.cmd.short:
description: "Short description of add command"
one: "add command creates a new command"
add.cmd.long:
description: "Long description of add command"
one: "add command creates a new command in specified directory with locales"
add.cmd.example:
description: "Examples"
one: "$ charmil add cliname"

add.flag.cmdPath.name:
description: "command Path flag"
one: "cmdPath"
add.flag.cmdPath.description:
description: "description of command path"
one: "command path where you want to create the command"
add.flag.cmdName.name:
description: "command name"
one: "cmdName"
add.flag.cmdName.description:
description: "abc"
one: "name of the new command"
161 changes: 161 additions & 0 deletions cli/internal/cmd/add/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package add

import (
"fmt"
"html/template"
"io/fs"
"io/ioutil"
"os"
"path"
"path/filepath"

"github.com/aerogear/charmil/cli/internal/common/modname"
"github.com/aerogear/charmil/cli/internal/factory"
"github.com/aerogear/charmil/cli/internal/template/add"
"github.com/aerogear/charmil/core/color"
"github.com/spf13/cobra"
)

// TemplateData defines fields that will store all the data used for generating templates
type TemplateData struct {
// Stores value of the `CmdPath` local flag. Default Value: "."
CmdPath string

// Stores value of the `CmdName` local flag
CmdName string

// Stores the name of the root module (extracted from go.mod file)
ModName string
}

// Initializes a zero-valued struct
var tmplData = TemplateData{}

func AddCommand(f *factory.Factory) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: f.Localizer.LocalizeByID("add.cmd.use"),
Short: f.Localizer.LocalizeByID("add.cmd.short"),
Long: f.Localizer.LocalizeByID("add.cmd.long"),
Example: f.Localizer.LocalizeByID("add.cmd.example"),
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Extracts the module name from `go.mod` file and stores it
modName, err := modname.GetModuleName()
if err != nil {
return err
}
tmplData.ModName = modName

if err := generateCommand(); err != nil {
return err
}

f.Logger.Infof(color.Success("%s command has been created in %s directory\n"), tmplData.CmdName, tmplData.CmdPath+"/"+tmplData.CmdName)

return nil

},
}

// Adds local flags
cmd.Flags().StringVarP(&tmplData.CmdPath, f.Localizer.LocalizeByID("add.flag.cmdPath.name"), "c", ".", f.Localizer.LocalizeByID("add.flag.cmdPath.description"))
cmd.Flags().StringVarP(&tmplData.CmdName, f.Localizer.LocalizeByID("add.flag.cmdName.name"), "s", "", f.Localizer.LocalizeByID("add.flag.cmdName.description"))

// Marks the `cmdName` flag as required.
// This causes the add command to report an
// error if invoked without the `cmdName` flag.
err := cmd.MarkFlagRequired("cmdName")
if err != nil {
return nil, err
}

return cmd, nil
}

// generateCommand function generates a command with it's locales file
func generateCommand() error {

// create a directory with command name
if mkdirerr := os.MkdirAll(path.Join(tmplData.CmdPath, tmplData.CmdName), 0755); mkdirerr != nil {
return mkdirerr
}

// walk through add templates folder
err := fs.WalkDir(add.AddTemplates, ".", func(p string, info fs.DirEntry, err error) error {
if err != nil {
return err
}

// creating templates from add templates folder
// to the cmdPath directory(provided by user)
var buf []byte
if info.Name() == "cmdname.en.yaml" || info.Name() == "cmdname.tmpl" {
buf, err = add.AddTemplates.ReadFile(info.Name())
if err != nil {
return err
}
var ext string

if info.Name() == "cmdname.en.yaml" {
ext = ".en.yaml"
} else {
ext = ".go"
}

err = ioutil.WriteFile(path.Join(tmplData.CmdPath, tmplData.CmdName, tmplData.CmdName+ext), buf, 0600)
if err != nil {
fmt.Printf("Unable to write file: %v", err)
}
}

// apply templates according to tmplData
err = applyTemplates()
return err

})

if err != nil {
return fmt.Errorf("failed to walk directory: %w", err)
}

return nil
}

// applyTemplates parses the files and apply templates
func applyTemplates() error {
err := filepath.Walk(path.Join(tmplData.CmdPath, tmplData.CmdName),
func(path string, info os.FileInfo, err error) error {

if err != nil {
return err
}

fi, err := os.Stat(path)
if err != nil {
return fmt.Errorf("failed to read file info: %w", err)
}

if fi.IsDir() {
return nil
}

tmpl, tmplErr := template.ParseFiles(path)
if tmplErr != nil {
return fmt.Errorf("failed to parse template: %w", err)
}

f, PathErr := os.Create(path)
if PathErr != nil {
return fmt.Errorf("failed to create file: %w", err)
}

// apply templateContext to the folder
if err := tmpl.Execute(f, tmplData); err != nil {
return fmt.Errorf("failed to execute template: %w", err)
}

return nil
})

return err
}
48 changes: 5 additions & 43 deletions cli/internal/cmd/crud/crud.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package crud

import (
"fmt"
"html/template"
"io/fs"
"io/ioutil"
"os"
"path/filepath"

"golang.org/x/mod/modfile"
"github.com/aerogear/charmil/cli/internal/common/generate"
"github.com/aerogear/charmil/cli/internal/common/modname"

"github.com/aerogear/charmil/cli/internal/factory"
"github.com/aerogear/charmil/cli/internal/template/crud"
Expand Down Expand Up @@ -47,7 +46,7 @@ func CrudCommand(f *factory.Factory) (*cobra.Command, error) {
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Extracts the module name from `go.mod` file and stores it
modName, err := GetModuleName()
modName, err := modname.GetModuleName()
if err != nil {
return err
}
Expand Down Expand Up @@ -128,11 +127,8 @@ func generateCrudPackages() error {

return nil
})
if err != nil {
return err
}

return nil
return err
}

// generateCrudFile takes the target file name, target path and the path
Expand Down Expand Up @@ -164,44 +160,10 @@ func generateCrudFile(fileName, currentPath, targetPath string) error {
}

// Generate CRUD file from the current template
err = generateFileFromTemplate(fileName, targetPath, string(buf), tmplData)
err = generate.GenerateFileFromTemplate(fileName, targetPath, string(buf), tmplData)
if err != nil {
return err
}

return nil
}

// generateFileFromTemplate uses the template to generate a
// new file using the specified file name and output path
func generateFileFromTemplate(name, path, tmplContent string, placeholderData interface{}) error {
// Creates a new file using the specified name and path
f, err := os.Create(fmt.Sprintf("%s/%s", path, name))
if err != nil {
return err
}
defer f.Close()

// Adds content to the generated file using the specified template
tmpl := template.Must(template.New(name).Parse(tmplContent))
err = tmpl.Execute(f, placeholderData)
if err != nil {
return err
}

return nil
}

// GetModuleName returns the module name extracted from the `go.mod` file
func GetModuleName() (string, error) {
// Stores the contents of `go.mod` file as a byte array
goModBytes, err := ioutil.ReadFile("go.mod")
if err != nil {
return "", err
}

// Extracts module name from the passed `go.mod` file contents
modName := modfile.ModulePath(goModBytes)

return modName, nil
}
9 changes: 9 additions & 0 deletions cli/internal/cmd/root/root.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package root

import (
"fmt"

"github.com/aerogear/charmil/cli/internal/cmd/add"
initialize "github.com/aerogear/charmil/cli/internal/cmd/init"
"github.com/aerogear/charmil/cli/internal/factory"
"github.com/spf13/cobra"
Expand All @@ -17,5 +20,11 @@ func NewRootCommand(f *factory.Factory) *cobra.Command {

cmd.AddCommand(initialize.InitCommand(f))

addCmd, err := add.AddCommand(f)
if err != nil {
fmt.Println(err)
}
cmd.AddCommand(addCmd)

return cmd
}
27 changes: 27 additions & 0 deletions cli/internal/common/generate/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package generate

import (
"fmt"
"html/template"
"os"
)

// GenerateFileFromTemplate uses the template to generate a
// new file using the specified file name and output path
func GenerateFileFromTemplate(name, path, tmplContent string, placeholderData interface{}) error {
// Creates a new file using the specified name and path
f, err := os.Create(fmt.Sprintf("%s/%s", path, name))
if err != nil {
return err
}
defer f.Close()

// Adds content to the generated file using the specified template
tmpl := template.Must(template.New(name).Parse(tmplContent))
err = tmpl.Execute(f, placeholderData)
if err != nil {
return err
}

return nil
}
21 changes: 21 additions & 0 deletions cli/internal/common/modname/modname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package modname

import (
"io/ioutil"

"golang.org/x/mod/modfile"
)

// GetModuleName returns the module name extracted from the `go.mod` file
func GetModuleName() (string, error) {
// Stores the contents of `go.mod` file as a byte array
goModBytes, err := ioutil.ReadFile("go.mod")
if err != nil {
return "", err
}

// Extracts module name from the passed `go.mod` file contents
modName := modfile.ModulePath(goModBytes)

return modName, nil
}
12 changes: 12 additions & 0 deletions cli/internal/template/add/cmdname.en.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{ .CmdName }}.cmd.use:
description: "Use of {{ .CmdName }} command"
one: "{{ .CmdName }}"
{{ .CmdName }}.cmd.short:
description: "Short description of {{ .CmdName }} command"
one: "describe {{ .CmdName }}"
{{ .CmdName }}.cmd.long:
description: "Long description of {{ .CmdName }} command"
one: "describe {{ .CmdName }}"
{{ .CmdName }}.cmd.example:
description: "Examples"
one: "$ give examples of {{ .CmdName }}"
21 changes: 21 additions & 0 deletions cli/internal/template/add/cmdname.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package {{ .CmdName }}

import (
"{{ .ModName }}/internal/factory"
"github.com/spf13/cobra"
)

func {{ .CmdName }}(f *factory.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: f.Localizer.LocalizeByID("{{ .CmdName }}.cmd.use"),
Short: f.Localizer.LocalizeByID("{{ .CmdName }}.cmd.short"),
Long: f.Localizer.LocalizeByID("{{ .CmdName }}.cmd.long"),
Example: f.Localizer.LocalizeByID("{{ .CmdName }}.cmd.example"),
RunE: func(cmd *cobra.Command, args []string) error {

return nil
},
}

return cmd
}
7 changes: 7 additions & 0 deletions cli/internal/template/add/tmpl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package add

import "embed"

// AddTemplates stores embedded contents of all the add template files
//go:embed *
var AddTemplates embed.FS
Loading

0 comments on commit f706d09

Please sign in to comment.