Skip to content

Commit

Permalink
UPDATES #45; Implement pipe & template
Browse files Browse the repository at this point in the history
This commit adds the abillity to use cli pipes to pipe templates into
`fakedata`. It also adds all generators as named functions to the
template funcMap.
  • Loading branch information
KevinGimbel committed Jun 7, 2017
1 parent 89cab85 commit 5508aa1
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 27 deletions.
29 changes: 23 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package main
import (
"bytes"
"fmt"
"github.com/lucapette/fakedata/pkg/fakedata"
flag "github.com/spf13/pflag"
"io/ioutil"
"math/rand"
"os"
"time"

"github.com/lucapette/fakedata/pkg/fakedata"
flag "github.com/spf13/pflag"
)

var version = "master"
Expand Down Expand Up @@ -65,8 +65,27 @@ func main() {
os.Exit(0)
}

rand.Seed(time.Now().UnixNano())

if *templateFlag != "" {
fakedata.ParseTemplate(*templateFlag)
fakedata.ParseTemplate(*templateFlag, *limitFlag)
os.Exit(0)
}

stat, _ := os.Stdin.Stat()
// Check if template data is piped to fakedata
if (stat.Mode() & os.ModeCharDevice) == 0 {

This comment has been minimized.

Copy link
@KevinGimbel

KevinGimbel Jun 7, 2017

Author Collaborator

I did some research and it appears that this is the way to check if there's data from a CLI pipe, e.g echo "{{ Name }}" | fakedata --limit 1 - my initial idea was to see if os.Stdin holds data but thats not so easy. If we use ioutil.ReadAll(os.Stdin) without this check fakedata will wait for input forever. Adding a timeout was not a good idea in my opinion.

This comment has been minimized.

Copy link
@jorinvo

jorinvo Jun 7, 2017

Contributor

Nice! Let's hope this works on all systems. Not sure if this works on Windows...

This comment has been minimized.

Copy link
@lucapette

lucapette Jun 7, 2017

Owner

interesting! Thank you for the research. Maybe worth putting behind a func isPiped() bool for the sake of clarity

This comment has been minimized.

Copy link
@KevinGimbel

KevinGimbel Jun 8, 2017

Author Collaborator

@jorinvo I'm not sure about this, too - can you test Windows systems? I only have access to a Mac and currently no virtualbox setup.

t, err := ioutil.ReadAll(os.Stdin)

if err != nil {
fmt.Println("Unable to read input: %s", err)
os.Exit(0)
}

err = fakedata.ParseTemplateFromPipe(string(t), *limitFlag)
if err != nil {
fmt.Println(err)
}
os.Exit(0)
}

Expand All @@ -75,8 +94,6 @@ func main() {
os.Exit(0)
}

rand.Seed(time.Now().UnixNano())

This comment has been minimized.

Copy link
@KevinGimbel

KevinGimbel Jun 7, 2017

Author Collaborator

I had to move rand.Seed above the template flag so generators from within a template would get proper randomness. :)

This comment has been minimized.

Copy link
@lucapette

lucapette Jun 7, 2017

Owner

Makes sense! 👍


columns, err := fakedata.NewColumns(flag.Args())
if err != nil {
fmt.Printf("%v\n\nSee fakedata --generators for a list of available generators.\n", err)
Expand Down
136 changes: 115 additions & 21 deletions pkg/fakedata/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,13 @@ package fakedata
import (
"fmt"
"html/template"
"io"
"os"
"strings"
)

var fakeColumn = Column{"Fake", "fakecolumn", ""}

// var generatorFunctions template.FuncMap
var generatorFunctions = template.FuncMap{
"Name": func() string {
return generators["name"].Func(fakeColumn)
},
"Email": func() string {
return generators["email"].Func(fakeColumn)
},
"Int": func(a, b int) string {
return generators["int"].Func(Column{"Int", "int", fmt.Sprintf("%d..%d", a, b)})
},
"Enum": func(keywords ...string) string {
constraints := createConstraints(keywords)
return generators["enum"].Func(Column{"Enum", "enu", constraints})
},
"Generator": func(name string) string {
return generators[name].Func(fakeColumn)
},
"Loop": func(i int) []int {
c := make([]int, i)

Expand All @@ -43,6 +27,99 @@ var generatorFunctions = template.FuncMap{
}
return false
},
"Date": func() string {
return generators["date"].Func(Column{Name: "tmplDate", Key: "tmplDateKey", Constraints: ""})
},
"DomainTld": func() string {
return generators["domain.tld"].Func(Column{Name: "tmplDomainTld", Key: "tmplDomainTld", Constraints: ""})
},
"DomainName": func() string {
return generators["domain.name"].Func(Column{Name: "tmplDomainName", Key: "tmplDomainName", Constraints: ""})
},
"Country": func() string {
return generators["country"].Func(Column{Name: "tmplCountry", Key: "tmplCountryKey", Constraints: ""})
},
"CountryCode": func() string {
return generators["country.code"].Func(Column{Name: "tmplCountryCode", Key: "tmplCountryCode", Constraints: ""})
},
"State": func() string {
return generators["state"].Func(Column{Name: "tmplState", Key: "tmplStateKey", Constraints: ""})
},
"Timezone": func() string {
return generators["timezone"].Func(Column{Name: "tmplTimezone", Key: "tmplTimezoneKey", Constraints: ""})
},
"Username": func() string {
return generators["username"].Func(Column{Name: "tmplUsername", Key: "tmplUsernameKey", Constraints: ""})
},
"NameFirst": func() string {
return generators["name.first"].Func(Column{Name: "tmplNameFirst", Key: "tmplNameFirstKey", Constraints: ""})
},
"NameLast": func() string {
return generators["name.last"].Func(Column{Name: "tmplNameLast", Key: "tmplNameLastKey", Constraints: ""})
},
"Color": func() string {
return generators["color"].Func(Column{Name: "tmplColor", Key: "tmplColorKey", Constraints: ""})
},
"ProductCategory": func() string {
return generators["product.category"].Func(Column{Name: "tmplProductCategory", Key: "tmplProductCategoryKey", Constraints: ""})
},
"ProductName": func() string {
return generators["product.name"].Func(Column{Name: "tmplProductName", Key: "tmplProductNameKey", Constraints: ""})
},
"EventAction": func() string {
return generators["event.action"].Func(Column{Name: "tmplEventAction", Key: "tmplEventActionKey", Constraints: ""})
},
"HTTPMethod": func() string {
return generators["http.method"].Func(Column{Name: "tmplHTTPMethod", Key: "tmplHTTPMethodKey", Constraints: ""})
},
"Name": func() string {
return generators["name"].Func(Column{Name: "tmplName", Key: "tmplNameKey", Constraints: " "})
},
"Email": func() string {
return generators["email"].Func(Column{Name: "tmplEmail", Key: "tmplEmailKey", Constraints: " "})
},
"Domain": func() string {
return generators["domain"].Func(Column{Name: "tmplDomain", Key: "tmplDomainKey", Constraints: ""})
},
"IPv4": func() string {
return generators["ipv4"].Func(Column{Name: "tmplIPv4", Key: "tmplIPv4Key", Constraints: ""})
},
"IPv6": func() string {
return generators["ipv6"].Func(Column{Name: "tmplIPv6", Key: "tmplIPv6Key", Constraints: ""})
},
"MacAddress": func() string {
return generators["mac.address"].Func(Column{Name: "tmplMacAddress", Key: "tmplMacAddressKey", Constraints: ""})
},
"Latitude": func() string {
return generators["latitude"].Func(Column{Name: "tmplLatitude", Key: "tmplLatitudeKey", Constraints: ""})
},
"Longitude": func() string {
return generators["longitude"].Func(Column{Name: "tmplLongitude", Key: "tmplLongitudeKey", Constraints: ""})
},
"Double": func() string {
return generators["double"].Func(Column{Name: "tmplDouble", Key: "tmplDoubleKey", Constraints: ""})
},
"Int": func(params ...int) string {
constraint := ".."
a := 0
b := 1000
if len(params) == 1 {
a = params[0]
}
if len(params) == 2 {
a = params[0]
b = params[1]
}
constraint = fmt.Sprintf("%d..%d", a, b)
return generators["int"].Func(Column{"Int", "int", constraint})
},
"Enum": func(keywords ...string) string {
constraints := createConstraints(keywords)
return generators["enum"].Func(Column{"Enum", "enu", constraints})
},
"File": func(path string) string {
return generators["file"].Func(Column{Name: "tmplA", Key: "tmplAKey", Constraints: path})
},

This comment has been minimized.

Copy link
@lucapette

lucapette Jun 7, 2017

Owner

I wonder how to handle this change in the context of #48 which heavily changed the way generators are structured. Probably we can move the entire FuncMap inside the factory I introduced. No matter how we do it though we have to decide which feature lands on master first. I'm inclined to think we should merge this feature first (despite creating more work for me 😆 ).

This comment has been minimized.

Copy link
@KevinGimbel

KevinGimbel Jun 8, 2017

Author Collaborator

The only change on my side would be to remove all the columns, right? So change every function from

func(path string) string {
	return generators["file"].Func(Column{Name: "tmplA", Key: "tmplAKey", Constraints: path})
 }

to

func(path string) string {
	return generators["file"].Func()
 }

This comment has been minimized.

Copy link
@lucapette

lucapette Jun 8, 2017

Owner

A bit more complicated than that as in #48 there isn't a global generators variable. I think it makes sense to proceed with what you're doing, landing it to master when we're happy and then I deal with the changes in #48

}

func createConstraints(params []string) string {
Expand All @@ -63,13 +140,30 @@ func splitPathName(r rune) bool {

// ParseTemplate takes a path to a template file as argument. It parses the template file and executes it on
// os.Stdout.
func ParseTemplate(path string) {
func ParseTemplate(path string, limit int) (err error) {
tn := getTemplateNameFromPath(path)
tmp, err := template.New(tn).Funcs(generatorFunctions).ParseFiles(path)
if err != nil {
fmt.Println(err)
return err
}
b := io.Writer(os.Stdout)
for i := 1; i <= limit; i++ {
tmp.Execute(b, nil)
}

tmp.Execute(os.Stdout, generators)
return nil
}

func ParseTemplateFromPipe(t string, limit int) (err error) {

This comment has been minimized.

Copy link
@KevinGimbel

KevinGimbel Jun 7, 2017

Author Collaborator

The ParseTemplateFromPipe function takes a template directly as input. This function is called when CLI pipes are detected in main.

tmp, err := template.New("stdin").Funcs(generatorFunctions).Parse(t)

if err != nil {
return err
}
b := io.Writer(os.Stdout)
for i := 1; i <= limit; i++ {
tmp.Execute(b, nil)
}

return nil
}

0 comments on commit 5508aa1

Please sign in to comment.