summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/ponzu/cli_test.go65
-rw-r--r--cmd/ponzu/generate.go192
-rw-r--r--cmd/ponzu/main.go5
-rw-r--r--cmd/ponzu/options.go153
4 files changed, 262 insertions, 153 deletions
diff --git a/cmd/ponzu/cli_test.go b/cmd/ponzu/cli_test.go
new file mode 100644
index 0000000..76feac3
--- /dev/null
+++ b/cmd/ponzu/cli_test.go
@@ -0,0 +1,65 @@
+package main
+
+import "testing"
+
+func TestParseType(t *testing.T) {
+ // blog title:string Author:string PostCategory:string content:string some_thing:int
+ args := []string{
+ "blog", "title:string", "Author:string",
+ "PostCategory:string", "content:string",
+ "some_thing:int", "Some_otherThing:float64",
+ }
+
+ gt, err := parseType(args)
+ if err != nil {
+ t.Errorf("Failed: %s", err.Error())
+ }
+
+ if gt.Name != "Blog" {
+ t.Errorf("Expected %s, got: %s", "Blog", gt.Name)
+ }
+}
+
+func TestFieldJSONName(t *testing.T) {
+ cases := map[string]string{
+ "_T": "t",
+ "T": "t",
+ "_tT_": "t_t_",
+ "TestCapsNoSym": "test_caps_no_sym",
+ "test_Some_caps_Sym": "test_some_caps_sym",
+ "testnocaps": "testnocaps",
+ "_Test_Caps_Sym_odd": "test_caps_sym_odd",
+ "test-hyphen": "test-hyphen",
+ "Test-hyphen-Caps": "test-hyphen-caps",
+ "Test-Hyphen_Sym-Caps": "test-hyphen_sym-caps",
+ }
+
+ for input, expected := range cases {
+ output := fieldJSONName(input)
+ if output != expected {
+ t.Errorf("Expected: %s, got: %s", expected, output)
+ }
+ }
+}
+
+func TestFieldName(t *testing.T) {
+ cases := map[string]string{
+ "_T": "T",
+ "T": "T",
+ "_tT_": "TT",
+ "TestCapsNoSym": "TestCapsNoSym",
+ "test_Some_caps_Sym": "TestSomeCapsSym",
+ "testnocaps": "Testnocaps",
+ "_Test_Caps_Sym_odd": "TestCapsSymOdd",
+ "test-hyphen": "TestHyphen",
+ "Test-hyphen-Caps": "TestHyphenCaps",
+ "Test-Hyphen_Sym-Caps": "TestHyphenSymCaps",
+ }
+
+ for input, expected := range cases {
+ output := fieldName(input)
+ if output != expected {
+ t.Errorf("Expected: %s, got: %s", expected, output)
+ }
+ }
+}
diff --git a/cmd/ponzu/generate.go b/cmd/ponzu/generate.go
new file mode 100644
index 0000000..9325d1d
--- /dev/null
+++ b/cmd/ponzu/generate.go
@@ -0,0 +1,192 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/format"
+ "html/template"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+type generateType struct {
+ Name string
+ Initial string
+ Fields []generateField
+}
+
+type generateField struct {
+ Name string
+ TypeName string
+ JSONName string
+}
+
+// blog title:string Author:string PostCategory:string content:string some_thing:int
+func parseType(args []string) (generateType, error) {
+ t := generateType{
+ Name: fieldName(args[0]),
+ }
+ t.Initial = strings.ToLower(string(t.Name[0]))
+
+ fields := args[1:]
+ for _, field := range fields {
+ f, err := parseField(field)
+ if err != nil {
+ return generateType{}, err
+ }
+
+ t.Fields = append(t.Fields, f)
+ }
+
+ return t, nil
+}
+
+func parseField(raw string) (generateField, error) {
+ // title:string
+ if !strings.Contains(raw, ":") {
+ return generateField{}, fmt.Errorf("Invalid generate argument. [%s]", raw)
+ }
+
+ pair := strings.Split(raw, ":")
+ field := generateField{
+ Name: fieldName(pair[0]),
+ TypeName: strings.ToLower(pair[1]),
+ JSONName: fieldJSONName(pair[0]),
+ }
+
+ return field, nil
+}
+
+// get the initial field name passed and check it for all possible cases
+// MyTitle:string myTitle:string my_title:string -> MyTitle
+// error-message:string -> ErrorMessage
+func fieldName(name string) string {
+ // remove _ or - if first character
+ if name[0] == '-' || name[0] == '_' {
+ name = name[1:]
+ }
+
+ // remove _ or - if last character
+ if name[len(name)-1] == '-' || name[len(name)-1] == '_' {
+ name = name[:len(name)-1]
+ }
+
+ // upcase the first character
+ name = strings.ToUpper(string(name[0])) + name[1:]
+
+ // remove _ or - character, and upcase the character immediately following
+ for i := 0; i < len(name); i++ {
+ r := rune(name[i])
+ if isUnderscore(r) || isHyphen(r) {
+ up := strings.ToUpper(string(name[i+1]))
+ name = name[:i] + up + name[i+2:]
+ }
+ }
+
+ return name
+}
+
+// get the initial field name passed and convert to json-like name
+// MyTitle:string myTitle:string my_title:string -> my_title
+// error-message:string -> error-message
+func fieldJSONName(name string) string {
+ // remove _ or - if first character
+ if name[0] == '-' || name[0] == '_' {
+ name = name[1:]
+ }
+
+ // downcase the first character
+ name = strings.ToLower(string(name[0])) + name[1:]
+
+ // check for uppercase character, downcase and insert _ before it if i-1
+ // isn't already _ or -
+ for i := 0; i < len(name); i++ {
+ r := rune(name[i])
+ if isUpper(r) {
+ low := strings.ToLower(string(r))
+ if name[i-1] == '_' || name[i-1] == '-' {
+ name = name[:i] + low + name[i+1:]
+ } else {
+ name = name[:i] + "_" + low + name[i+1:]
+ }
+ }
+ }
+
+ return name
+}
+
+func isUpper(char rune) bool {
+ if char >= 'A' && char <= 'Z' {
+ return true
+ }
+
+ return false
+}
+
+func isUnderscore(char rune) bool {
+ return char == '_'
+}
+
+func isHyphen(char rune) bool {
+ return char == '-'
+}
+
+func generateContentType(args []string, path string) error {
+ name := args[0]
+ fileName := strings.ToLower(name) + ".go"
+
+ // open file in ./content/ dir
+ // if exists, alert user of conflict
+ pwd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+
+ if path != "" {
+ pwd = path
+ }
+
+ contentDir := filepath.Join(pwd, "content")
+ filePath := filepath.Join(contentDir, fileName)
+
+ if _, err := os.Stat(filePath); !os.IsNotExist(err) {
+ return fmt.Errorf("Please remove '%s' before executing this command.", fileName)
+ }
+
+ // no file exists.. ok to write new one
+ file, err := os.Create(filePath)
+ defer file.Close()
+ if err != nil {
+ return err
+ }
+
+ // parse type info from args
+ gt, err := parseType(args)
+ if err != nil {
+ return fmt.Errorf("Failed to parse type args: %s", err.Error())
+ }
+
+ tmpl, err := template.ParseFiles("contentType.tmpl")
+ if err != nil {
+ return fmt.Errorf("Failed to parse template: %s", err.Error())
+ }
+
+ buf := &bytes.Buffer{}
+ err = tmpl.Execute(buf, gt)
+ if err != nil {
+ return fmt.Errorf("Failed to execute template: %s", err.Error())
+ }
+
+ fmtBuf, err := format.Source(buf.Bytes())
+ if err != nil {
+ return fmt.Errorf("Failed to format template: %s", err.Error())
+ }
+
+ _, err = file.Write(fmtBuf)
+ if err != nil {
+ return fmt.Errorf("Failed to write generated file buffer: %s", err.Error())
+ }
+
+ return nil
+}
diff --git a/cmd/ponzu/main.go b/cmd/ponzu/main.go
index 234d3b6..88355f4 100644
--- a/cmd/ponzu/main.go
+++ b/cmd/ponzu/main.go
@@ -120,11 +120,12 @@ func main() {
os.Exit(0)
}
- err := generateContentType(args[1], "")
+ err := generateContentType(args[1:], "")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
+
case "build":
err := buildPonzuServer(args)
if err != nil {
@@ -197,8 +198,10 @@ func main() {
}
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
+
case "":
flag.PrintDefaults()
+
default:
flag.PrintDefaults()
}
diff --git a/cmd/ponzu/options.go b/cmd/ponzu/options.go
index 244a759..375893d 100644
--- a/cmd/ponzu/options.go
+++ b/cmd/ponzu/options.go
@@ -3,7 +3,6 @@ package main
import (
"errors"
"fmt"
- "html/template"
"io"
"io/ioutil"
"os"
@@ -12,143 +11,6 @@ import (
"strings"
)
-func generateContentType(name, path string) error {
- fileName := strings.ToLower(name) + ".go"
- typeName := strings.ToUpper(string(name[0])) + string(name[1:])
-
- // contain processed name an info for template
- data := map[string]string{
- "name": typeName,
- "initial": string(fileName[0]),
- }
-
- // open file in ./content/ dir
- // if exists, alert user of conflict
- pwd, err := os.Getwd()
- if err != nil {
- return err
- }
-
- if path != "" {
- pwd = path
- }
-
- contentDir := filepath.Join(pwd, "content")
- filePath := filepath.Join(contentDir, fileName)
-
- if _, err := os.Stat(filePath); !os.IsNotExist(err) {
- return fmt.Errorf("Please remove '%s' before executing this command.", fileName)
- }
-
- // no file exists.. ok to write new one
- file, err := os.Create(filePath)
- defer file.Close()
- if err != nil {
- return err
- }
-
- // execute template
- tmpl := template.Must(template.New("content").Parse(contentTypeTmpl))
- err = tmpl.Execute(file, data)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-const contentTypeTmpl = `
-package content
-
-import (
- "fmt"
-
- "github.com/bosssauce/ponzu/management/editor"
-)
-
-// {{ .name }} is the generic content struct
-type {{ .name }} struct {
- Item
- editor editor.Editor
-
- // required: all maintained {{ .name }} fields must have json tags!
- Title string ` + "`json:" + `"title"` + "`" + `
- Content string ` + "`json:" + `"content"` + "`" + `
- Author string ` + "`json:" + `"author"` + "`" + `
- Photo string ` + "`json:" + `"photo"` + "`" + `
- Category []string ` + "`json:" + `"category"` + "`" + `
- Theme string ` + "`json:" + `"theme"` + "`" + `
-}
-
-// MarshalEditor writes a buffer of html to edit a {{ .name }}
-// partially implements editor.Editable
-func ({{ .initial }} *{{ .name }}) MarshalEditor() ([]byte, error) {
- view, err := editor.Form({{ .initial }},
- editor.Field{
- // Take note that the first argument to these Input-like methods
- // is the string version of each {{ .name }} field, and must follow
- // this pattern for auto-decoding and auto-encoding reasons.
- View: editor.Input("Title", {{ .initial }}, map[string]string{
- "label": "{{ .name }} Title",
- "type": "text",
- "placeholder": "Enter your {{ .name }} Title here",
- }),
- },
- editor.Field{
- View: editor.Richtext("Content", {{ .initial }}, map[string]string{
- "label": "Content",
- "placeholder": "Add the content of your {{ .name }} here",
- }),
- },
- editor.Field{
- View: editor.Input("Author", {{ .initial }}, map[string]string{
- "label": "Author",
- "type": "text",
- "placeholder": "Enter the author name here",
- }),
- },
- editor.Field{
- View: editor.File("Photo", {{ .initial }}, map[string]string{
- "label": "Author Photo",
- "placeholder": "Upload a profile picture for the author",
- }),
- },
- editor.Field{
- View: editor.Tags("Category", {{ .initial }}, map[string]string{
- "label": "{{ .name }} Category",
- }),
- },
- editor.Field{
- View: editor.Select("Theme", {{ .initial }}, map[string]string{
- "label": "Theme Style",
- }, map[string]string{
- "dark": "Dark",
- "light": "Light",
- }),
- },
- )
-
- if err != nil {
- return nil, fmt.Errorf("Failed to render {{ .name }} editor view: %s", err.Error())
- }
-
- return view, nil
-}
-
-func init() {
- Types["{{ .name }}"] = func() interface{} { return new({{ .name }}) }
-}
-
-// ContentName is required to set the display name for a piece of content in the editor
-// Partially implements editor.Editable
-func ({{ .initial }} *{{ .name }}) ContentName() string { return {{ .initial }}.Title }
-
-// Editor is a buffer of bytes for the Form function to write input views
-// partially implements editor.Editable
-func ({{ .initial }} *{{ .name }}) Editor() *editor.Editor { return &{{ .initial }}.editor }
-
-`
-
func newProjectInDir(path string) error {
// set path to be nested inside $GOPATH/src
gopath := os.Getenv("GOPATH")
@@ -230,12 +92,6 @@ func createProjInDir(path string) error {
return err
}
- err = generateContentType("post", path)
- if err != nil {
- // TODO: rollback, remove ponzu project from path
- return err
- }
-
fmt.Println("Dev build cloned from " + local + ":ponzu-dev")
return nil
}
@@ -278,12 +134,6 @@ func createProjInDir(path string) error {
return err
}
- err = generateContentType("post", path)
- if err != nil {
- // TODO: rollback, remove ponzu project from path
- return err
- }
-
gitDir := filepath.Join(path, ".git")
err = os.RemoveAll(gitDir)
if err != nil {
@@ -311,8 +161,7 @@ func vendorCorePackages(path string) error {
}
}
- // create a user 'content' package, and give it a single 'post.go' file
- // using generateContentType("post")
+ // create a user 'content' package
contentPath := filepath.Join(path, "content")
err = os.Mkdir(contentPath, os.ModeDir|os.ModePerm)
if err != nil {