add: io reader composition
This commit is contained in:
		
							
								
								
									
										21
									
								
								interfaces/15-io-reader-composition/book.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								interfaces/15-io-reader-composition/book.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										35
									
								
								interfaces/15-io-reader-composition/counter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								interfaces/15-io-reader-composition/counter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										112
									
								
								interfaces/15-io-reader-composition/database.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								interfaces/15-io-reader-composition/database.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| // } | ||||
							
								
								
									
										54
									
								
								interfaces/15-io-reader-composition/database.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								interfaces/15-io-reader-composition/database.json
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| 		} | ||||
| 	} | ||||
| ] | ||||
							
								
								
									
										
											BIN
										
									
								
								interfaces/15-io-reader-composition/database.json.gz
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								interfaces/15-io-reader-composition/database.json.gz
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										12
									
								
								interfaces/15-io-reader-composition/game.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								interfaces/15-io-reader-composition/game.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										40
									
								
								interfaces/15-io-reader-composition/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								interfaces/15-io-reader-composition/list.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										97
									
								
								interfaces/15-io-reader-composition/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								interfaces/15-io-reader-composition/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										16
									
								
								interfaces/15-io-reader-composition/money.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								interfaces/15-io-reader-composition/money.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										23
									
								
								interfaces/15-io-reader-composition/product.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								interfaces/15-io-reader-composition/product.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
| } | ||||
							
								
								
									
										12
									
								
								interfaces/15-io-reader-composition/puzzle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								interfaces/15-io-reader-composition/puzzle.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										77
									
								
								interfaces/15-io-reader-composition/timestamp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								interfaces/15-io-reader-composition/timestamp.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
							
								
								
									
										12
									
								
								interfaces/15-io-reader-composition/toy.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								interfaces/15-io-reader-composition/toy.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user