From 46e1f947a9b33cf2edd163c48de1a08318d8ad4a Mon Sep 17 00:00:00 2001 From: Jean Niklas L'orange Date: Sat, 30 Jan 2016 17:09:34 +0100 Subject: [PATCH] More examples --- edn_tags.go | 7 ++- example_arbitrary_keys_test.go | 51 ++++++++++++++++ example_poly_test.go | 95 ++++++++++++++++++++++++++++++ example_set_all_test.go | 103 +++++++++++++++++++++++++++++++++ example_stream_test.go | 58 +++++++++++++++++++ example_test.go | 41 ++++++++++--- 6 files changed, 344 insertions(+), 11 deletions(-) create mode 100644 example_arbitrary_keys_test.go create mode 100644 example_poly_test.go create mode 100644 example_set_all_test.go create mode 100644 example_stream_test.go diff --git a/edn_tags.go b/edn_tags.go index 2e12aef..1623af2 100644 --- a/edn_tags.go +++ b/edn_tags.go @@ -8,9 +8,10 @@ // self-contained examples of go-edn can be found at // https://github.com/go-edn/edn/tree/v1/examples. // -// Note that the examples in this package is not checking errors as persively as -// you should do when you use this package. This is done because I'd like the -// examples to be easily readable and understandable. +// Note that the small examples in this package is not checking errors as +// persively as you should do when you use this package. This is done because +// I'd like the examples to be easily readable and understandable. The bigger +// examples provide proper error handling. package edn import ( diff --git a/example_arbitrary_keys_test.go b/example_arbitrary_keys_test.go new file mode 100644 index 0000000..666244a --- /dev/null +++ b/example_arbitrary_keys_test.go @@ -0,0 +1,51 @@ +package edn_test + +import ( + "fmt" + + "gopkg.in/edn.v1" +) + +type Point3 struct { + X, Y, Z int64 +} + +type Unit struct { + Type edn.Keyword + HP int +} + +// EDN, in contrast to JSON, supports arbitrary values as keys. +func Example_arbitraryKeys() { + input := `{{:x 1 :y 2 :z 3} "Greybeard" + {:y 10 :x 1 :z -10} "Blackwind"}` + + var locStarships map[Point3]string + err := edn.UnmarshalString(input, &locStarships) + if err != nil { + panic(err) + } + + p := Point3{1, 10, -10} + + fmt.Printf("Starship at location %v is %s\n", p, locStarships[p]) + + input = `{[0 2] {:type :scout :hp 55} + [-3 10] {:type :villager :hp 25} + [5 5] {:type :bowman :hp 32} + [5 6] {:type :bowman :hp 29}}` + + var locUnits map[[2]int]Unit + err = edn.UnmarshalString(input, &locUnits) + if err != nil { + panic(err) + } + + loc := [2]int{5, 5} + + fmt.Printf("Unit at location %v is %+v\n", loc, locUnits[loc]) + + // Output: + // Starship at location {1 10 -10} is Blackwind + // Unit at location [5 5] is {Type::bowman HP:32} +} diff --git a/example_poly_test.go b/example_poly_test.go new file mode 100644 index 0000000..d565279 --- /dev/null +++ b/example_poly_test.go @@ -0,0 +1,95 @@ +package edn_test + +import ( + "fmt" + "strings" + + "gopkg.in/edn.v1" +) + +type Notifiable interface { + Notify() +} + +type User struct { + Username string +} + +func (u User) MarshalEDN() ([]byte, error) { + return edn.Marshal(edn.Tag{"myapp/user", u.Username}) +} + +func (u *User) Notify() { + fmt.Printf("Notified user %s.\n", u.Username) +} + +type Group struct { + GroupId int +} + +func (g Group) MarshalEDN() ([]byte, error) { + return edn.Marshal(edn.Tag{"myapp/group", g.GroupId}) +} + +func (g *Group) Notify() { + fmt.Printf("Notified group with id %d.\n", g.GroupId) +} + +var notifyTagMap edn.TagMap + +// We use a tagMap to avoid adding these values to the entire system. +func init() { + err := notifyTagMap.AddTagFn("myapp/user", func(s string) (*User, error) { + return &User{s}, nil + }) + if err != nil { + panic(err) + } + + err = notifyTagMap.AddTagFn("myapp/group", func(id int) (*Group, error) { + return &Group{id}, nil + }) + if err != nil { + panic(err) + } +} + +// This example shows how to read and write basic EDN tags, and how this can be +// utilised: In contrast to encoding/json, you can read in data where you only +// know that the input satisfies some sort of interface, provided the value is +// tagged. +func Example_polymorphicTags() { + input := `[#myapp/user "eugeness" + #myapp/group 10 + #myapp/user "jeannikl" + #myapp/user "jeremiah" + #myapp/group 100]` + + rdr := strings.NewReader(input) + dec := edn.NewDecoder(rdr) + dec.UseTagMap(¬ifyTagMap) + + var toNotify []Notifiable + err := dec.Decode(&toNotify) + if err != nil { + panic(err) + } + for _, notify := range toNotify { + notify.Notify() + } + + // Print out the values as well + out, err := edn.Marshal(toNotify) + if err != nil { + panic(err) + } + + fmt.Println(string(out)) + // Output: + // Notified user eugeness. + // Notified group with id 10. + // Notified user jeannikl. + // Notified user jeremiah. + // Notified group with id 100. + // [#myapp/user"eugeness" #myapp/group 10 #myapp/user"jeannikl" #myapp/user"jeremiah" #myapp/group 100] +} diff --git a/example_set_all_test.go b/example_set_all_test.go new file mode 100644 index 0000000..2d9c75c --- /dev/null +++ b/example_set_all_test.go @@ -0,0 +1,103 @@ +package edn_test + +import ( + "fmt" + + "gopkg.in/edn.v1" +) + +type UserOption edn.Keyword + +type UnknownUserOptionError UserOption + +func (err UnknownUserOptionError) Error() string { + return fmt.Sprintf("Unknown user option %s", edn.Keyword(err)) +} + +const ( + UnknownOption = UserOption("") + ShowEmail = UserOption("show-email") + Notifications = UserOption("notifications") + DailyEmail = UserOption("daily-email") + RememberMe = UserOption("remember-me") +) + +func ListUserOptions() []UserOption { + return []UserOption{ + ShowEmail, + Notifications, + DailyEmail, + RememberMe, + } +} + +func (uo *UserOption) UnmarshalEDN(bs []byte) error { + var kw edn.Keyword + err := edn.Unmarshal(bs, &kw) + if err != nil { + return err + } + opt := UserOption(kw) + switch opt { + case ShowEmail, Notifications, DailyEmail, RememberMe: + *uo = opt + return nil + default: + return UnknownUserOptionError(opt) + } +} + +type UserOptions map[UserOption]bool + +func (opts *UserOptions) UnmarshalEDN(bs []byte) error { + var kw edn.Keyword + // try to decode into keyword first + err := edn.Unmarshal(bs, &kw) + if err == nil && kw == edn.Keyword("all") { + // Put all options into the user option map + *opts = UserOptions(make(map[UserOption]bool)) + for _, opt := range ListUserOptions() { + (*opts)[opt] = true + } + return nil + } + // then try to decode into user map + var rawOpts map[UserOption]bool + err = edn.Unmarshal(bs, &rawOpts) + *opts = UserOptions(rawOpts) + return err +} + +// This example shows how one can implement enums and sets, and how to support +// multiple different forms for a specific value type. The set implemented here +// supports the notation `:all` for all values. +func Example_enumsAndSets() { + inputs := []string{ + "#{:show-email :notifications}", + "#{:notifications :show-email :remember-me}", + ":all", + "#{:doot-doot}", + ":none", + "#{} ;; no options", + } + for _, input := range inputs { + var opts UserOptions + err := edn.UnmarshalString(input, &opts) + if err != nil { + fmt.Println(err) + // Do proper error handling here if something fails + continue + } + // Cannot print out a map, as its ordering is nondeterministic. + fmt.Printf("show email? %t, notifications? %t, daily email? %t, remember me? %t\n", + opts[ShowEmail], opts[Notifications], opts[DailyEmail], opts[RememberMe]) + } + + // Output: + // show email? true, notifications? true, daily email? false, remember me? false + // show email? true, notifications? true, daily email? false, remember me? true + // show email? true, notifications? true, daily email? true, remember me? true + // Unknown user option :doot-doot + // edn: cannot unmarshal keyword into Go value of type map[edn_test.UserOption]bool + // show email? false, notifications? false, daily email? false, remember me? false +} diff --git a/example_stream_test.go b/example_stream_test.go new file mode 100644 index 0000000..a48712a --- /dev/null +++ b/example_stream_test.go @@ -0,0 +1,58 @@ +package edn_test + +import ( + "fmt" + "io" + "strings" + + "gopkg.in/edn.v1" +) + +// Typically, you'd also include timestamps here. Imagine that they are here. +type LogEntry struct { + Level edn.Keyword + Message string `edn:"msg"` + Environment string `edn:"env"` + Service string +} + +// This example shows how one can do streaming with the decoder, and how to +// properly know when the stream has no elements left. +func Example_streaming() { + const input = ` +{:level :debug :msg "1 < 2 ? true" :env "dev" :service "comparer"} +{:level :warn :msg "slow response time from 127.0.1.39" :env "prod" :service "worker 10"} +{:level :warn :msg "worker 8 has been unavailable for 30s" :env "prod" :service "gateway"} +{:level :info :msg "new processing request: what.png" :env "prod" :service "gateway"} +{:level :debug :msg "1 < nil? error" :env "dev" :service "comparer"} +{:level :warn :msg "comparison failed: 1 < nil" :env "dev" :service "comparer"} +{:level :info :msg "received new processing request: what.png" :env "prod" :service "worker 3"} +{:level :warn :msg "bad configuration value :timeout, using 3h" :env "staging" :service "worker 3"} +` + + rdr := strings.NewReader(input) + dec := edn.NewDecoder(rdr) + var err error + for { + var entry LogEntry + err = dec.Decode(&entry) + if err != nil { + break + } + if entry.Level == edn.Keyword("warn") && entry.Environment != "dev" { + fmt.Println(entry.Message) + } + } + if err != nil && err != io.EOF { + // Something bad happened to our reader + fmt.Println(err) + return + } + // If err == io.EOF then we've reached end of stream + fmt.Println("End of stream!") + // Output: + // slow response time from 127.0.1.39 + // worker 8 has been unavailable for 30s + // bad configuration value :timeout, using 3h + // End of stream! +} diff --git a/example_test.go b/example_test.go index b4e7238..3129daf 100644 --- a/example_test.go +++ b/example_test.go @@ -14,18 +14,24 @@ func ExampleDecoder_AddTagFn_duration() { rdr := strings.NewReader(input) dec := edn.NewDecoder(rdr) - err := dec.AddTagFn("com.myapp/duration", time.ParseDuration) - if err != nil { - panic(err) - } + dec.AddTagFn("com.myapp/duration", time.ParseDuration) var d time.Duration - err = dec.Decode(&d) + dec.Decode(&d) + fmt.Println(d) + + input = `#com.myapp/duration "1moment"` + rdr = strings.NewReader(input) + dec = edn.NewDecoder(rdr) + dec.AddTagFn("com.myapp/duration", time.ParseDuration) + err := dec.Decode(&d) if err != nil { - panic(err) + fmt.Println(err) } - fmt.Println(d) - // Output: 2h30m0s + + // Output: + // 2h30m0s + // time: unknown unit moment in duration 1moment } func ExampleDecoder_AddTagFn_complex() { @@ -197,3 +203,22 @@ func ExampleKeyword() { // :friday // It is friday! } + +func ExampleRune() { + runeSlice := []edn.Rune{'a', 'b', 'c', ',', ' ', '\n'} + + bs, _ := edn.Marshal(runeSlice) + + fmt.Println(string(bs)) + // Output: [\a \b \c \u002c \space \newline] +} + +func ExampleTag_reading() { + input := "#unknown ???" + + var tag edn.Tag + edn.UnmarshalString(input, &tag) + + fmt.Printf("Tag with name %s and value %q of type %T\n", tag.Tagname, tag.Value, tag.Value) + // Output: Tag with name unknown and value "???" of type edn.Symbol +}