From 9d5aa09b1eb8c9a31b4bdaa20f5f567d348f79ad Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Mon, 9 Sep 2019 18:14:50 +0300 Subject: [PATCH] add: io reader composition --- interfaces/15-io-reader-composition/book.go | 21 ++++ .../15-io-reader-composition/counter.go | 35 ++++++ .../15-io-reader-composition/database.go | 112 ++++++++++++++++++ .../15-io-reader-composition/database.json | 54 +++++++++ .../15-io-reader-composition/database.json.gz | Bin 0 -> 230 bytes interfaces/15-io-reader-composition/game.go | 12 ++ interfaces/15-io-reader-composition/list.go | 40 +++++++ interfaces/15-io-reader-composition/main.go | 97 +++++++++++++++ interfaces/15-io-reader-composition/money.go | 16 +++ .../15-io-reader-composition/product.go | 23 ++++ interfaces/15-io-reader-composition/puzzle.go | 12 ++ .../15-io-reader-composition/timestamp.go | 77 ++++++++++++ interfaces/15-io-reader-composition/toy.go | 12 ++ 13 files changed, 511 insertions(+) create mode 100644 interfaces/15-io-reader-composition/book.go create mode 100644 interfaces/15-io-reader-composition/counter.go create mode 100644 interfaces/15-io-reader-composition/database.go create mode 100644 interfaces/15-io-reader-composition/database.json create mode 100644 interfaces/15-io-reader-composition/database.json.gz create mode 100644 interfaces/15-io-reader-composition/game.go create mode 100644 interfaces/15-io-reader-composition/list.go create mode 100644 interfaces/15-io-reader-composition/main.go create mode 100644 interfaces/15-io-reader-composition/money.go create mode 100644 interfaces/15-io-reader-composition/product.go create mode 100644 interfaces/15-io-reader-composition/puzzle.go create mode 100644 interfaces/15-io-reader-composition/timestamp.go create mode 100644 interfaces/15-io-reader-composition/toy.go diff --git a/interfaces/15-io-reader-composition/book.go b/interfaces/15-io-reader-composition/book.go new file mode 100644 index 0000000..c36372f --- /dev/null +++ b/interfaces/15-io-reader-composition/book.go @@ -0,0 +1,21 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "fmt" +) + +type book struct { + product + Published timestamp +} + +func (b *book) String() string { + return fmt.Sprintf("%s - (%s)", &b.product, b.Published) +} diff --git a/interfaces/15-io-reader-composition/counter.go b/interfaces/15-io-reader-composition/counter.go new file mode 100644 index 0000000..ebe327a --- /dev/null +++ b/interfaces/15-io-reader-composition/counter.go @@ -0,0 +1,35 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import "io" + +// counter counts the total number of bytes read. +type counter struct { + r io.Reader + n int +} + +// newCounter wraps `r` and returns a *counter. +func newCounter(r io.Reader) *counter { + return &counter{r: r} +} + +// Total bytes read. +func (c *counter) total() int { + return c.n +} + +// Read counts the number of bytes read from the underlying reader. +func (c *counter) Read(p []byte) (n int, err error) { + n, err = c.r.Read(p) // read from the underlying reader + if n > 0 { + c.n += n // sum the number of bytes read with the total bytes counted + } + return n, err // return the number of bytes read and an error +} diff --git a/interfaces/15-io-reader-composition/database.go b/interfaces/15-io-reader-composition/database.go new file mode 100644 index 0000000..5b65d6a --- /dev/null +++ b/interfaces/15-io-reader-composition/database.go @@ -0,0 +1,112 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "encoding/json" + "fmt" + "io" + "reflect" +) + +type database struct { + list *list + types map[string]item +} + +// load the list by decoding the data from any kind of data source. +func (db *database) load(r io.Reader) error { + return json.NewDecoder(r).Decode(db) +} + +func (db *database) MarshalJSON() ([]byte, error) { + type encodable struct { + Type string + Item item + } + + var e []encodable + + for _, it := range *db.list { + t := reflect.TypeOf(it).Elem().Name() + e = append(e, encodable{t, it}) + } + + return json.Marshal(e) +} + +func (db *database) UnmarshalJSON(data []byte) error { + var decodables []struct { + Type string + Item json.RawMessage + } + + if err := json.Unmarshal(data, &decodables); err != nil { + return err + } + + for _, d := range decodables { + it, err := db.newItem(d.Item, d.Type) + if err != nil { + return err + } + + *db.list = append(*db.list, it) + } + + return nil +} + +func (db *database) newItem(data []byte, typ string) (item, error) { + it, ok := db.types[typ] + if !ok { + return nil, fmt.Errorf("newItem: type (%q) does not exist", typ) + } + + t := reflect.TypeOf(it) + e := t.Elem() + v := reflect.New(e) + it = v.Interface().(item) + + return it, json.Unmarshal(data, &it) +} + +func (db *database) register(typ string, it item) { + if db.types == nil { + db.types = make(map[string]item) + } + db.types[typ] = it +} + +// I put it here as a reference implementation of manually reading from a reader. +// func (db *database) loadX(r io.Reader) error { +// buf := make([]byte, 64) +// +// var ( +// data []byte +// n int +// err error +// ) +// +// for { +// n, err = r.Read(buf) +// +// if n > 0 { +// data = append(data, buf[:n]...) +// } +// +// if err != nil { +// break +// } +// } +// +// if err != io.EOF { +// return err +// } +// return json.Unmarshal(data, db) +// } diff --git a/interfaces/15-io-reader-composition/database.json b/interfaces/15-io-reader-composition/database.json new file mode 100644 index 0000000..5c9516c --- /dev/null +++ b/interfaces/15-io-reader-composition/database.json @@ -0,0 +1,54 @@ +[ + { + "Type": "book", + "Item": { + "Title": "moby dick", + "Price": 10, + "Published": 118281600 + } + }, + { + "Type": "book", + "Item": { + "Title": "odyssey", + "Price": 15, + "Published": 733622400 + } + }, + { + "Type": "book", + "Item": { + "Title": "hobbit", + "Price": 25, + "Published": -62135596800 + } + }, + { + "Type": "puzzle", + "Item": { + "Title": "rubik's cube", + "Price": 5 + } + }, + { + "Type": "game", + "Item": { + "Title": "minecraft", + "Price": 20 + } + }, + { + "Type": "game", + "Item": { + "Title": "tetris", + "Price": 5 + } + }, + { + "Type": "toy", + "Item": { + "Title": "yoda", + "Price": 150 + } + } +] \ No newline at end of file diff --git a/interfaces/15-io-reader-composition/database.json.gz b/interfaces/15-io-reader-composition/database.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..fca1d7342f96c9117708cd918215734b8f945877 GIT binary patch literal 230 zcmV6@aVX&Hu;kj3Ao zvt$f(Pek5`=9C0s6s>4H>MHA|v8@xfs%DVQl$+K}^qkR1=_x5DD+{=Yv`UVHH;k?P gRH%inKz+GROEcAFBu@sP_!{OMZcrYqc7p-{02!BQ?f?J) literal 0 HcmV?d00001 diff --git a/interfaces/15-io-reader-composition/game.go b/interfaces/15-io-reader-composition/game.go new file mode 100644 index 0000000..3604b15 --- /dev/null +++ b/interfaces/15-io-reader-composition/game.go @@ -0,0 +1,12 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +type game struct { + product +} diff --git a/interfaces/15-io-reader-composition/list.go b/interfaces/15-io-reader-composition/list.go new file mode 100644 index 0000000..69da963 --- /dev/null +++ b/interfaces/15-io-reader-composition/list.go @@ -0,0 +1,40 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "fmt" + "strings" +) + +type item interface { + discount(float64) + fmt.Stringer +} + +type list []item + +func (l list) String() string { + if len(l) == 0 { + return "Sorry. We're waiting for delivery 🚚." + } + + var str strings.Builder + for _, it := range l { + str.WriteString(it.String()) + str.WriteRune('\n') + } + + return str.String() +} + +func (l list) discount(ratio float64) { + for _, it := range l { + it.discount(ratio) + } +} diff --git a/interfaces/15-io-reader-composition/main.go b/interfaces/15-io-reader-composition/main.go new file mode 100644 index 0000000..812e47c --- /dev/null +++ b/interfaces/15-io-reader-composition/main.go @@ -0,0 +1,97 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "compress/gzip" + "fmt" + "net/http" +) + +func main() { + /* + store := list{ + &book{product{"moby dick", 10}, toTimestamp(118281600)}, + &book{product{"odyssey", 15}, toTimestamp("733622400")}, + &book{product{"hobbit", 25}, unknown}, + &puzzle{product{"rubik's cube", 5}}, + &game{product{"minecraft", 20}}, + &game{product{"tetris", 5}}, + &toy{product{"yoda", 150}}, + } + + db := database{list: &store} + + out, err := json.MarshalIndent(&db, "", "\t") + if err != nil { + log.Fatalln(err) + } + + fmt.Println(string(out)) + + // store.discount(.5) + // fmt.Print(store) + */ + + var store list + + db := database{list: &store} + db.register("book", new(book)) + db.register("book", new(book)) + db.register("game", new(game)) + db.register("puzzle", new(puzzle)) + db.register("toy", new(toy)) + + // load from a file + // f, _ := os.Open("database.json") + // db.load(f) + // f.Close() + + // load from a string + // const data = `[ + // { "Type": "book", "Item": { "Title": "1984", "Price": 8, "Published": -649641600 } }, + // { "Type": "game", "Item": { "Title": "paperboy", "Price": 20 } }]` + // r := strings.NewReader(data) + // db.load(r) + + // load from a web server + // res, _ := http.Get("https://inancgumus.github.io/x/database.json") + // db.load(res.Body) + // res.Body.Close() + + // decompress the reader while reading from it + // res, _ := http.Get("https://inancgumus.github.io/x/database.json.gz") + // gr, _ := gzip.NewReader(res.Body) + // db.load(gr) + // gr.Close() + // res.Body.Close() + + // decompress and count the number of compressed bytes read + // res, _ := http.Get("https://inancgumus.github.io/x/database.json.gz") + // c := newCounter(res.Body) // count the bytes read + // gr, _ := gzip.NewReader(c) + + // db.load(gr) + // fmt.Printf("%d bytes read.\n\n", c.total()) + + // gr.Close() + // res.Body.Close() + + // count the number of compressed bytes read from the web server then decompress + res, _ := http.Get("https://inancgumus.github.io/x/database.json.gz") + gr, _ := gzip.NewReader(res.Body) + c := newCounter(gr) + + db.load(c) + fmt.Printf("%d bytes read.\n\n", c.total()) + + gr.Close() + res.Body.Close() + + fmt.Print(store) +} diff --git a/interfaces/15-io-reader-composition/money.go b/interfaces/15-io-reader-composition/money.go new file mode 100644 index 0000000..095a039 --- /dev/null +++ b/interfaces/15-io-reader-composition/money.go @@ -0,0 +1,16 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import "fmt" + +type money float64 + +func (m money) String() string { + return fmt.Sprintf("$%.2f", m) +} diff --git a/interfaces/15-io-reader-composition/product.go b/interfaces/15-io-reader-composition/product.go new file mode 100644 index 0000000..f93cb84 --- /dev/null +++ b/interfaces/15-io-reader-composition/product.go @@ -0,0 +1,23 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import "fmt" + +type product struct { + Title string + Price money +} + +func (p *product) String() string { + return fmt.Sprintf("%-15s: %s", p.Title, p.Price) +} + +func (p *product) discount(ratio float64) { + p.Price *= money(1 - ratio) +} diff --git a/interfaces/15-io-reader-composition/puzzle.go b/interfaces/15-io-reader-composition/puzzle.go new file mode 100644 index 0000000..acbaa47 --- /dev/null +++ b/interfaces/15-io-reader-composition/puzzle.go @@ -0,0 +1,12 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +type puzzle struct { + product +} diff --git a/interfaces/15-io-reader-composition/timestamp.go b/interfaces/15-io-reader-composition/timestamp.go new file mode 100644 index 0000000..28629d8 --- /dev/null +++ b/interfaces/15-io-reader-composition/timestamp.go @@ -0,0 +1,77 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "strconv" + "time" +) + +// Mon Jan 2 15:04:05 -0700 MST 2006 +const layout = "2006/01" + +// unknown is the zero value of a timestamp. +var unknown = timestamp(time.Time{}) + +// timestamp prints timestamps, it's a stringer. +// It is useful even if it's zero. +type timestamp time.Time + +// MarshalJSON from timestamp. +func (ts timestamp) MarshalJSON() (out []byte, err error) { + u := time.Time(ts).Unix() + return strconv.AppendInt(out, u, 10), nil +} + +// UnmarshalJSON data to timestamp. +func (ts *timestamp) UnmarshalJSON(data []byte) error { + s := string(data) + + // Let the ParseInt parse quoted strings. + us, err := strconv.Unquote(s) + if err == nil { + s = us + } + + // Always overwrite the timestamp when decoding. + *ts = unknown + + // Handle the numeric case. + if n, err := strconv.ParseInt(s, 10, 64); err == nil { + *ts = timestamp(time.Unix(n, 0)) + } + + return nil +} + +// String representation of the timestamp. +func (ts timestamp) String() string { + t := time.Time(ts) + + if t.IsZero() { + return "unknown" + } + + return t.Format(layout) +} + +// toTimestamp from an unknown value. +func toTimestamp(v interface{}) timestamp { + var t int + + switch v := v.(type) { + case int: + t = v + case string: + t, _ = strconv.Atoi(v) + default: + return unknown + } + + return timestamp(time.Unix(int64(t), 0)) +} diff --git a/interfaces/15-io-reader-composition/toy.go b/interfaces/15-io-reader-composition/toy.go new file mode 100644 index 0000000..ab5613d --- /dev/null +++ b/interfaces/15-io-reader-composition/toy.go @@ -0,0 +1,12 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +type toy struct { + product +}