add: new interfaces
This commit is contained in:
		
							
								
								
									
										73
									
								
								interfaces/_legacy/01-variadic-funcs/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								interfaces/_legacy/01-variadic-funcs/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // 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" | ||||
|  | ||||
| func main() { | ||||
| 	nums := []int{2, 3, 7} | ||||
| 	fmt.Printf("nums            : %d\n", nums) | ||||
|  | ||||
| 	n := avgNoVariadic(nums) | ||||
| 	fmt.Printf("avgNoVariadic   : %d\n", n) | ||||
|  | ||||
| 	n = avg(2, 3, 7) | ||||
| 	fmt.Printf("avg(2, 3, 7)    : %d\n", n) | ||||
|  | ||||
| 	n = avg(2, 3, 7, 8) | ||||
| 	fmt.Printf("avg(2, 3, 7, 8) : %d\n", n) | ||||
|  | ||||
| 	// use ... to pass a slice | ||||
| 	n = avg(nums...) | ||||
| 	fmt.Printf("avg(nums...)    : %d\n", n) | ||||
|  | ||||
| 	// uses the existing slice | ||||
| 	double(nums...) | ||||
| 	fmt.Printf("double(nums...) : %d\n", nums) | ||||
|  | ||||
| 	// creates a new slice | ||||
| 	double(4, 6, 14) | ||||
| 	fmt.Printf("double(4, 6, 14): %d\n", nums) | ||||
|  | ||||
| 	// creates a nil slice | ||||
| 	fmt.Printf("\nmain.nums       : %p\n", nums) | ||||
| 	investigate("passes main.nums", nums...) | ||||
| 	investigate("passes main.nums", nums...) | ||||
| 	investigate("passes args", 4, 6, 14) | ||||
| 	investigate("passes args", 4, 6, 14) | ||||
| 	investigate("no args") | ||||
| } | ||||
|  | ||||
| func investigate(msg string, nums ...int) { | ||||
| 	fmt.Printf("investigate.nums: %12p  ->  %s\n", nums, msg) | ||||
|  | ||||
| 	if len(nums) > 0 { | ||||
| 		fmt.Printf("\tfirst element: %d\n", nums[0]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func double(nums ...int) { | ||||
| 	for i := range nums { | ||||
| 		nums[i] *= 2 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func avg(nums ...int) int { | ||||
| 	return sum(nums) / len(nums) | ||||
| } | ||||
|  | ||||
| func avgNoVariadic(nums []int) int { | ||||
| 	return sum(nums) / len(nums) | ||||
| } | ||||
|  | ||||
| func sum(nums []int) (total int) { | ||||
| 	for _, n := range nums { | ||||
| 		total += n | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										52
									
								
								interfaces/_legacy/02-func-values/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								interfaces/_legacy/02-func-values/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // 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 filterFunc func(int) bool | ||||
|  | ||||
| func main() { | ||||
| 	signatures() | ||||
| 	funcValues() | ||||
| } | ||||
|  | ||||
| func isEven(n int) bool { | ||||
| 	return n%2 == 0 | ||||
| } | ||||
|  | ||||
| func isOdd(m int) bool { | ||||
| 	return m%2 == 1 | ||||
| } | ||||
|  | ||||
| func signatures() { | ||||
| 	fmt.Println("••• FUNC SIGNATURES (TYPES) •••") | ||||
| 	fmt.Printf("isEven     : %T\n", isEven) | ||||
| 	fmt.Printf("isOdd      : %T\n", isOdd) | ||||
| } | ||||
|  | ||||
| func funcValues() { | ||||
| 	fmt.Println("\n••• FUNC VALUES (VARS) •••") | ||||
|  | ||||
| 	// var value func(int) bool | ||||
| 	var value filterFunc | ||||
| 	fmt.Printf("value nil? : %t\n", value == nil) | ||||
|  | ||||
| 	value = isEven | ||||
| 	fmt.Printf("value(2)   : %t\n", value(2)) | ||||
| 	fmt.Printf("isEven(2)  : %t\n", isEven(2)) | ||||
|  | ||||
| 	value = isOdd | ||||
| 	fmt.Printf("value(1)   : %t\n", value(1)) | ||||
| 	fmt.Printf("isOdd(1)   : %t\n", isOdd(1)) | ||||
|  | ||||
| 	fmt.Printf("value nil? : %t\n", value == nil) | ||||
| 	fmt.Printf("value      : %p\n", value) | ||||
| 	fmt.Printf("isEven     : %p\n", isEven) | ||||
| 	fmt.Printf("isOdd      : %p\n", isOdd) | ||||
| } | ||||
							
								
								
									
										82
									
								
								interfaces/_legacy/03-func-to-func/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								interfaces/_legacy/03-func-to-func/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // 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" | ||||
| 	"unicode" | ||||
| ) | ||||
|  | ||||
| type filterFunc func(int) bool | ||||
|  | ||||
| func main() { | ||||
| 	signatures() | ||||
|  | ||||
| 	fmt.Println("\n••• WITHOUT FUNC VALUES •••") | ||||
| 	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} | ||||
|  | ||||
| 	fmt.Printf("evens      : %d\n", filterEvens(nums...)) | ||||
| 	fmt.Printf("odds       : %d\n", filterOdds(nums...)) | ||||
|  | ||||
| 	fmt.Println("\n••• FUNC VALUES •••") | ||||
| 	fmt.Printf("evens      : %d\n", filter(isEven, nums...)) | ||||
| 	fmt.Printf("odds       : %d\n", filter(isOdd, nums...)) | ||||
|  | ||||
| 	fmt.Println("\n••• MAPPING •••") | ||||
| 	fmt.Println(strings.Map(unpunct, "hello!!! HOW ARE YOU???? :))")) | ||||
| 	fmt.Println(strings.Map(unpunct, "TIME IS UP!")) | ||||
| } | ||||
|  | ||||
| func unpunct(r rune) rune { | ||||
| 	if unicode.IsPunct(r) { | ||||
| 		return -1 | ||||
| 	} | ||||
| 	return unicode.ToLower(r) | ||||
| } | ||||
|  | ||||
| func filter(f filterFunc, nums ...int) (filtered []int) { | ||||
| 	for _, n := range nums { | ||||
| 		if f(n) { | ||||
| 			filtered = append(filtered, n) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func filterOdds(nums ...int) (filtered []int) { | ||||
| 	for _, n := range nums { | ||||
| 		if isOdd(n) { | ||||
| 			filtered = append(filtered, n) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func filterEvens(nums ...int) (filtered []int) { | ||||
| 	for _, n := range nums { | ||||
| 		if isEven(n) { | ||||
| 			filtered = append(filtered, n) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func isEven(n int) bool { | ||||
| 	return n%2 == 0 | ||||
| } | ||||
|  | ||||
| func isOdd(m int) bool { | ||||
| 	return m%2 == 1 | ||||
| } | ||||
|  | ||||
| func signatures() { | ||||
| 	fmt.Println("••• FUNC SIGNATURES (TYPES) •••") | ||||
| 	fmt.Printf("isEven     : %T\n", isEven) | ||||
| 	fmt.Printf("isOdd      : %T\n", isOdd) | ||||
| } | ||||
							
								
								
									
										76
									
								
								interfaces/_legacy/04-closures/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								interfaces/_legacy/04-closures/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| // 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 filterFunc func(int) bool | ||||
|  | ||||
| func main() { | ||||
| 	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} | ||||
|  | ||||
| 	fmt.Println("••• FUNC VALUES •••") | ||||
| 	fmt.Printf("evens      : %d\n", filter(isEven, nums...)) | ||||
| 	fmt.Printf("odds       : %d\n", filter(isOdd, nums...)) | ||||
| 	fmt.Printf("> 3        : %d\n", filter(greaterThan3, nums...)) | ||||
| 	fmt.Printf("> 6        : %d\n", filter(greaterThan6, nums...)) | ||||
|  | ||||
| 	// ======================================================================== | ||||
|  | ||||
| 	fmt.Println("\n••• CLOSURES •••") | ||||
|  | ||||
| 	var min int | ||||
| 	greaterThan := func(n int) bool { | ||||
| 		return n > min | ||||
| 	} | ||||
|  | ||||
| 	min = 3 | ||||
| 	fmt.Printf("> 3        : %d\n", filter(greaterThan, nums...)) | ||||
|  | ||||
| 	min = 6 | ||||
| 	fmt.Printf("> 6        : %d\n", filter(greaterThan, nums...)) | ||||
|  | ||||
| 	// min = 1 | ||||
| 	// fmt.Printf("> 1        : %d\n", filter(greaterThan, nums...)) | ||||
| 	// min = 2 | ||||
| 	// fmt.Printf("> 2        : %d\n", filter(greaterThan, nums...)) | ||||
| 	// min = 3 | ||||
| 	// fmt.Printf("> 3        : %d\n", filter(greaterThan, nums...)) | ||||
|  | ||||
| 	var filterers []filterFunc | ||||
| 	for i := 1; i <= 3; i++ { | ||||
| 		current := i | ||||
|  | ||||
| 		filterers = append(filterers, func(n int) bool { | ||||
| 			min = current | ||||
| 			return greaterThan(n) | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	printer(filterers, nums...) | ||||
| } | ||||
|  | ||||
| func printer(filterers []filterFunc, nums ...int) { | ||||
| 	for i, filterer := range filterers { | ||||
| 		fmt.Printf("> %d        : %d\n", i+1, filter(filterer, nums...)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func greaterThan6(n int) bool { return n > 6 } | ||||
| func greaterThan3(n int) bool { return n > 3 } | ||||
| func isEven(n int) bool       { return n%2 == 0 } | ||||
| func isOdd(m int) bool        { return m%2 == 1 } | ||||
|  | ||||
| func filter(f filterFunc, nums ...int) (filtered []int) { | ||||
| 	for _, n := range nums { | ||||
| 		if f(n) { | ||||
| 			filtered = append(filtered, n) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										58
									
								
								interfaces/_legacy/05-higher-order-funcs/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								interfaces/_legacy/05-higher-order-funcs/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| // 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 filterFunc func(int) bool | ||||
|  | ||||
| func main() { | ||||
| 	fmt.Println("••• HIGHER-ORDER FUNCS •••") | ||||
|  | ||||
| 	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} | ||||
|  | ||||
| 	odd := reverse(reverse(isEven)) | ||||
| 	fmt.Printf("reversed   : %t\n", odd(8)) | ||||
|  | ||||
| 	fmt.Printf("> 3        : %d\n", filter(greater(3), nums...)) | ||||
| 	fmt.Printf("> 6        : %d\n", filter(greater(6), nums...)) | ||||
| 	fmt.Printf("<= 6       : %d\n", filter(lesseq(6), nums...)) | ||||
| 	fmt.Printf("<= 6       : %d\n", filter(reverse(greater(6)), nums...)) | ||||
| } | ||||
|  | ||||
| func reverse(f filterFunc) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		return !f(n) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func greater(min int) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		return n > min | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func lesseq(max int) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		return n <= max | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func filter(f filterFunc, nums ...int) (filtered []int) { | ||||
| 	for _, n := range nums { | ||||
| 		if f(n) { | ||||
| 			filtered = append(filtered, n) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func isEven(n int) bool { return n%2 == 0 } | ||||
| func isOdd(m int) bool  { return m%2 == 1 } | ||||
							
								
								
									
										97
									
								
								interfaces/_legacy/06-functional-programming/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								interfaces/_legacy/06-functional-programming/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 ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| type filterFunc func(int) bool | ||||
|  | ||||
| func main() { | ||||
| 	fmt.Println("••• HIGHER-ORDER FUNCS •••") | ||||
|  | ||||
| 	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} | ||||
|  | ||||
| 	odd := reverse(reverse(isEven)) | ||||
| 	fmt.Printf("reversed   : %t\n", odd(8)) | ||||
|  | ||||
| 	fmt.Printf("> 3        : %d\n", filter(greater(3), nums...)) | ||||
| 	fmt.Printf("> 6        : %d\n", filter(greater(6), nums...)) | ||||
| 	fmt.Printf("<= 6       : %d\n", filter(lesseq(6), nums...)) | ||||
| 	fmt.Printf("<= 6       : %d\n", filter(reverse(greater(6)), nums...)) | ||||
|  | ||||
| 	fmt.Println("\n••• CLOSURES •••") | ||||
| 	fmt.Printf("uniques    : %d\n", filter(uniques(), 1, 1, 2, 3, 3)) | ||||
|  | ||||
| 	dups := reverse(uniques()) | ||||
| 	fmt.Printf("dups       : %v\n", filter(dups, 1, 1, 2, 3, 3)) | ||||
|  | ||||
| 	dups = reverse(uniques()) | ||||
| 	fmt.Printf("dups dups  : %v\n", filter(dups, 1, 1, 2, 3, 3, 3, 3)) | ||||
|  | ||||
| 	dups = reverse(uniques()) | ||||
| 	out := filter(dups, 1, 1, 2, 3, 3, 3, 3) | ||||
| 	fmt.Printf("dups uniqs : %v\n", filter(uniques(), out...)) | ||||
|  | ||||
| 	dups = reverse(uniques()) | ||||
| 	chained := chain(reverse(greater(1)), dups, uniques()) | ||||
| 	fmt.Printf("dups chain : %v\n", filter(chained, 1, 1, 2, 3, 3, 3, 3, 0, 0)) | ||||
| } | ||||
|  | ||||
| func chain(filters ...filterFunc) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		for _, next := range filters { | ||||
| 			if !next(n) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func uniques() filterFunc { | ||||
| 	seen := make(map[int]bool) | ||||
|  | ||||
| 	return func(n int) (ok bool) { | ||||
| 		if !seen[n] { | ||||
| 			seen[n], ok = true, true | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func reverse(f filterFunc) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		return !f(n) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func greater(min int) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		return n > min | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func lesseq(max int) filterFunc { | ||||
| 	return func(n int) bool { | ||||
| 		return n <= max | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func filter(f filterFunc, nums ...int) (filtered []int) { | ||||
| 	for _, n := range nums { | ||||
| 		if f(n) { | ||||
| 			filtered = append(filtered, n) | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func isEven(n int) bool { return n%2 == 0 } | ||||
| func isOdd(m int) bool  { return m%2 == 1 } | ||||
							
								
								
									
										7
									
								
								interfaces/functional/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								interfaces/functional/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| SHELL := /bin/bash | ||||
|  | ||||
| r:  | ||||
| 	go run . < ../logs/log.txt | ||||
|  | ||||
| t:  | ||||
| 	time go run . < ../logs/log.txt | ||||
							
								
								
									
										46
									
								
								interfaces/functional/chartwriter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								interfaces/functional/chartwriter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
|  | ||||
| 	c "github.com/wcharczuk/go-chart" | ||||
| ) | ||||
|  | ||||
| func chartWriter(w io.Writer) outputFn { | ||||
| 	return func(res []result) error { | ||||
| 		return chartWrite(w, res) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func chartWrite(w io.Writer, res []result) error { | ||||
| 	sort.Slice(res, func(i, j int) bool { | ||||
| 		return res[i].domain > res[j].domain | ||||
| 	}) | ||||
|  | ||||
| 	donut := c.DonutChart{ | ||||
| 		Title: "Total Visits Per Domain", | ||||
| 		TitleStyle: c.Style{ | ||||
| 			FontSize:  35, | ||||
| 			Show:      true, | ||||
| 			FontColor: c.ColorAlternateGreen, | ||||
| 		}, | ||||
| 		Width:  1920, | ||||
| 		Height: 800, | ||||
| 	} | ||||
|  | ||||
| 	for _, r := range res { | ||||
| 		v := c.Value{ | ||||
| 			Label: r.domain + r.page + ": " + strconv.Itoa(r.visits), | ||||
| 			Value: float64(r.visits), | ||||
| 			Style: c.Style{ | ||||
| 				FontSize: 14, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		donut.Values = append(donut.Values, v) | ||||
| 	} | ||||
|  | ||||
| 	return donut.Render(c.SVG, w) | ||||
| } | ||||
							
								
								
									
										33
									
								
								interfaces/functional/field.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								interfaces/functional/field.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| ) | ||||
|  | ||||
| // field helps for field parsing | ||||
| type field struct{ err error } | ||||
|  | ||||
| // uatoi parses an unsigned integer string and saves the error. | ||||
| // it assumes that the val is unsigned. | ||||
| // for ease of usability: it returns an int instead of uint. | ||||
| func (f *field) uatoi(name, val string) int { | ||||
| 	n, err := strconv.Atoi(val) | ||||
| 	if err != nil || n < 0 { | ||||
| 		f.err = fmt.Errorf("incorrect field -> %q = %q", name, val) | ||||
| 	} | ||||
| 	return n | ||||
| } | ||||
|  | ||||
| func atoi(input []byte) (int, error) { | ||||
| 	val := 0 | ||||
| 	for i := 0; i < len(input); i++ { | ||||
| 		char := input[i] | ||||
| 		if char < '0' || char > '9' { | ||||
| 			return 0, errors.New("invalid number") | ||||
| 		} | ||||
| 		val = val*10 + int(char) - '0' | ||||
| 	} | ||||
| 	return val, nil | ||||
| } | ||||
							
								
								
									
										34
									
								
								interfaces/functional/filters.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								interfaces/functional/filters.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| package main | ||||
|  | ||||
| import "strings" | ||||
|  | ||||
| func noopFilter(r result) bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func notUsing(filter filterFn) filterFn { | ||||
| 	return func(r result) bool { | ||||
| 		return !filter(r) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func domainExtFilter(domains ...string) filterFn { | ||||
| 	return func(r result) bool { | ||||
| 		for _, domain := range domains { | ||||
| 			if strings.HasSuffix(r.domain, "."+domain) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func domainFilter(domain string) filterFn { | ||||
| 	return func(r result) bool { | ||||
| 		return strings.Contains(r.domain, domain) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func orgDomainsFilter(r result) bool { | ||||
| 	return strings.HasSuffix(r.domain, ".org") | ||||
| } | ||||
							
								
								
									
										20
									
								
								interfaces/functional/groupers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								interfaces/functional/groupers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| package main | ||||
|  | ||||
| // domainGrouper groups by domain. | ||||
| // but it keeps the other fields. | ||||
| // for example: it returns pages as well, but you shouldn't use them. | ||||
| // exercise: write a function that erases superfluous data. | ||||
| func domainGrouper(r result) string { | ||||
| 	return r.domain | ||||
| } | ||||
|  | ||||
| func pageGrouper(r result) string { | ||||
| 	return r.domain + r.page | ||||
| } | ||||
|  | ||||
| // groupBy allocates map unnecessarily | ||||
| func noopGrouper(r result) string { | ||||
| 	// with something like: | ||||
| 	// return randomStrings() | ||||
| 	return "" | ||||
| } | ||||
							
								
								
									
										44
									
								
								interfaces/functional/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								interfaces/functional/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| // 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" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	p := pipeline{ | ||||
| 		read:   textReader(os.Stdin), | ||||
| 		write:  textWriter(os.Stdout), | ||||
| 		filter: notUsing(domainExtFilter("io", "com")), | ||||
| 		group:  domainGrouper, | ||||
| 	} | ||||
|  | ||||
| 	// var p pipeline | ||||
| 	// p. | ||||
| 	// 	filterBy(notUsing(domainExtFilter("io", "com"))). | ||||
| 	// 	groupBy(domainGrouper) | ||||
|  | ||||
| 	if err := p.start(); err != nil { | ||||
| 		fmt.Println("> Err:", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // []outputter{textFile("results.txt"), chartFile("graph.png")} | ||||
|  | ||||
| // func outputs(w io.Writer) outputFn { | ||||
| // 	tw := textWriter(w) | ||||
| // 	cw := chartWriter(w) | ||||
|  | ||||
| // 	return func(rs []result) error { | ||||
| // 		err := tw(rs) | ||||
| // 		err = cw(rs) | ||||
| // 		return err | ||||
| // 	} | ||||
| // } | ||||
							
								
								
									
										78
									
								
								interfaces/functional/pipeline.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								interfaces/functional/pipeline.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| // 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 "os" | ||||
|  | ||||
| type ( | ||||
| 	processFn func(r result) | ||||
| 	inputFn   func(processFn) error | ||||
| 	outputFn  func([]result) error | ||||
| 	filterFn  func(result) (include bool) | ||||
| 	groupFn   func(result) (key string) | ||||
| ) | ||||
|  | ||||
| type pipeline struct { | ||||
| 	read   inputFn | ||||
| 	write  outputFn | ||||
| 	filter filterFn | ||||
| 	group  groupFn | ||||
| } | ||||
|  | ||||
| func (p *pipeline) filterBy(f filterFn) *pipeline { p.filter = f; return p } | ||||
| func (p *pipeline) groupBy(f groupFn) *pipeline   { p.group = f; return p } | ||||
| func (p *pipeline) from(f inputFn) *pipeline      { p.read = f; return p } | ||||
| func (p *pipeline) to(f outputFn) *pipeline       { p.write = f; return p } | ||||
|  | ||||
| func (p *pipeline) defaults() { | ||||
| 	if p.filter == nil { | ||||
| 		p.filter = noopFilter | ||||
| 	} | ||||
|  | ||||
| 	if p.group == nil { | ||||
| 		p.group = domainGrouper | ||||
| 	} | ||||
|  | ||||
| 	if p.read == nil { | ||||
| 		p.read = textReader(os.Stdin) | ||||
| 	} | ||||
|  | ||||
| 	if p.write == nil { | ||||
| 		p.write = textWriter(os.Stdout) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *pipeline) start() error { | ||||
| 	p.defaults() | ||||
|  | ||||
| 	// retrieve and process the lines | ||||
| 	sum := make(map[string]result) | ||||
|  | ||||
| 	process := func(r result) { | ||||
| 		if !p.filter(r) { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		k := p.group(r) | ||||
| 		sum[k] = r.add(sum[k]) | ||||
| 	} | ||||
|  | ||||
| 	// return err from input reader | ||||
| 	if err := p.read(process); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// prepare the results for outputting | ||||
| 	var out []result | ||||
| 	for _, res := range sum { | ||||
| 		out = append(out, res) | ||||
| 	} | ||||
|  | ||||
| 	// return err from output reader | ||||
| 	return p.write(out) | ||||
| } | ||||
							
								
								
									
										83
									
								
								interfaces/functional/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								interfaces/functional/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain  string | ||||
| 	page    string | ||||
| 	visits  int | ||||
| 	uniques int | ||||
| } | ||||
|  | ||||
| // add adds the metrics of another result | ||||
| func (r result) add(other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // parseFields parses and returns the parsing result | ||||
| func parseFields(line string) (r result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
|  | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong number of fields %q", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.page = fields[1] | ||||
|  | ||||
| 	f := new(field) | ||||
| 	r.visits = f.uatoi("visits", fields[2]) | ||||
| 	r.uniques = f.uatoi("uniques", fields[3]) | ||||
|  | ||||
| 	return r, f.err | ||||
| } | ||||
|  | ||||
| func fastParseFields(data []byte) (res result, err error) { | ||||
| 	const separator = ' ' | ||||
|  | ||||
| 	var findex int | ||||
|  | ||||
| 	for i, j := 0, 0; i < len(data); i++ { | ||||
| 		c := data[i] | ||||
| 		last := len(data) == i+1 | ||||
|  | ||||
| 		if c != separator && !last { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if last { | ||||
| 			i = len(data) | ||||
| 		} | ||||
|  | ||||
| 		switch fval := data[j:i]; findex { | ||||
| 		case 0: | ||||
| 			res.domain = string(fval) | ||||
| 		case 1: | ||||
| 			res.page = string(fval) | ||||
| 		case 2: | ||||
| 			res.visits, err = atoi(fval) | ||||
| 		case 3: | ||||
| 			res.uniques, err = atoi(fval) | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return res, err | ||||
| 		} | ||||
|  | ||||
| 		j = i + 1 | ||||
| 		findex++ | ||||
| 	} | ||||
|  | ||||
| 	if findex != fieldsLength { | ||||
| 		err = fmt.Errorf("wrong number of fields %q", data) | ||||
| 	} | ||||
| 	return res, err | ||||
| } | ||||
							
								
								
									
										39
									
								
								interfaces/functional/textreader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								interfaces/functional/textreader.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| // 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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| func textReader(r io.Reader) inputFn { | ||||
| 	return func(process processFn) error { | ||||
| 		var ( | ||||
| 			l  = 1 | ||||
| 			in = bufio.NewScanner(r) | ||||
| 		) | ||||
|  | ||||
| 		for in.Scan() { | ||||
| 			r, err := fastParseFields(in.Bytes()) | ||||
| 			// r, err := parseFields(in.Text()) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("line %d: %v", l, err) | ||||
| 			} | ||||
|  | ||||
| 			process(r) | ||||
| 			l++ | ||||
| 		} | ||||
|  | ||||
| 		if c, ok := r.(io.Closer); ok { | ||||
| 			c.Close() | ||||
| 		} | ||||
| 		return in.Err() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										50
									
								
								interfaces/functional/textwriter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								interfaces/functional/textwriter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // TODO: sort by result key interfaces section | ||||
|  | ||||
| const ( | ||||
|  | ||||
| 	//          DOMAINS PAGES VISITS UNIQUES | ||||
| 	//             ^      ^     ^    ^ | ||||
| 	//             |      |     |    | | ||||
| 	header     = "%-25s %-10s %10s %10s\n" | ||||
| 	line       = "%-25s %-10s %10d %10d\n" | ||||
| 	footer     = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES | ||||
| 	dash       = "-" | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| func textWriter(w io.Writer) outputFn { | ||||
| 	return func(res []result) error { | ||||
| 		sort.Slice(res, func(i, j int) bool { | ||||
| 			return res[i].domain > res[j].domain | ||||
| 		}) | ||||
|  | ||||
| 		var total result | ||||
|  | ||||
| 		fmt.Fprintf(w, header, "DOMAINS", "PAGES", "VISITS", "UNIQUES") | ||||
| 		fmt.Fprintf(w, strings.Repeat(dash, dashLength)+"\n") | ||||
|  | ||||
| 		for _, r := range res { | ||||
| 			total = total.add(r) | ||||
| 			fmt.Fprintf(w, line, r.domain, r.page, r.visits, r.uniques) | ||||
| 		} | ||||
|  | ||||
| 		fmt.Fprintf(w, footer, "", total.visits, total.uniques) | ||||
|  | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func noWhere() outputFn { | ||||
| 	return func(res []result) error { | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										6
									
								
								interfaces/logparser-testing/log.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								interfaces/logparser-testing/log.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| learngoprogramming.com 10 200 | ||||
| learngoprogramming.com 10 300 | ||||
| golang.org 4 50 | ||||
| golang.org 6 100 | ||||
| blog.golang.org 20 25 | ||||
| blog.golang.org 10 1 | ||||
							
								
								
									
										6
									
								
								interfaces/logparser-testing/log_err_missing.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								interfaces/logparser-testing/log_err_missing.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| learngoprogramming.com 10 200 | ||||
| learngoprogramming.com 10 | ||||
| golang.org 4 50 | ||||
| golang.org 6 100 | ||||
| blog.golang.org 20 25 | ||||
| blog.golang.org 10 1 | ||||
							
								
								
									
										6
									
								
								interfaces/logparser-testing/log_err_negative.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								interfaces/logparser-testing/log_err_negative.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| learngoprogramming.com 10 200 | ||||
| learngoprogramming.com 10 300 | ||||
| golang.org -100 50 | ||||
| golang.org 6 100 | ||||
| blog.golang.org 20 25 | ||||
| blog.golang.org 10 1 | ||||
							
								
								
									
										6
									
								
								interfaces/logparser-testing/log_err_str.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								interfaces/logparser-testing/log_err_str.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| learngoprogramming.com 10 200 | ||||
| learngoprogramming.com 10 THREE-HUNDRED | ||||
| golang.org FOUR 50 | ||||
| golang.org 6 100 | ||||
| blog.golang.org 20 25 | ||||
| blog.golang.org 10 1 | ||||
							
								
								
									
										26
									
								
								interfaces/logparser-testing/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								interfaces/logparser-testing/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // 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 ( | ||||
| 	"bufio" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/inancgumus/learngo/27-interfaces/logparser-testing/report" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	p := report.New() | ||||
|  | ||||
| 	in := bufio.NewScanner(os.Stdin) | ||||
| 	for in.Scan() { | ||||
| 		p.Parse(in.Text()) | ||||
| 	} | ||||
|  | ||||
| 	summarize(p.Summarize(), p.Err(), in.Err()) | ||||
| } | ||||
							
								
								
									
										59
									
								
								interfaces/logparser-testing/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								interfaces/logparser-testing/main_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| // +build integration | ||||
|  | ||||
| // go test -tags=integration | ||||
|  | ||||
| package main_test | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	okIn = ` | ||||
| a.com 1 2 | ||||
| b.com 3 4 | ||||
| a.com 4 5 | ||||
| b.com 6 7` | ||||
|  | ||||
| 	okOut = ` | ||||
| DOMAIN                             VISITS           TIME SPENT | ||||
| ----------------------------------------------------------------- | ||||
| a.com                                   5                    7 | ||||
| b.com                                   9                   11 | ||||
|  | ||||
| TOTAL                                  14                   18` | ||||
| ) | ||||
|  | ||||
| func TestSummary(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name, in, out string | ||||
| 	}{ | ||||
| 		{"valid input", okIn, okOut}, | ||||
| 		{"missing fields", "a.com 1 2\nb.com 3", "> Err: line #2: missing fields: [b.com 3]"}, | ||||
| 		{"incorrect visits", "a.com 1 2\nb.com -1 1", `> Err: line #2: incorrect visits: "-1"`}, | ||||
| 		{"incorrect time spent", "a.com 1 2\nb.com 3 -1", `> Err: line #2: incorrect time spent: "-1"`}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			run(t, strings.TrimSpace(tt.in), strings.TrimSpace(tt.out)) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func run(t *testing.T, in, out string) { | ||||
| 	cmd := exec.Command("go", "run", ".") | ||||
| 	cmd.Stdin = strings.NewReader(in) | ||||
|  | ||||
| 	got, err := cmd.CombinedOutput() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if !bytes.Equal(got, []byte(out+"\n")) { | ||||
| 		t.Fatalf("\nwant:\n%s\n\ngot:\n%s", out, got) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										52
									
								
								interfaces/logparser-testing/report/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								interfaces/logparser-testing/report/parser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // 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 report | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // Parser parses the log file and generates a summary report. | ||||
| type Parser struct { | ||||
| 	summary *Summary // summarizes the parsing results | ||||
| 	lines   int      // number of parsed lines (for the error messages) | ||||
| 	lerr    error    // the last error occurred | ||||
| } | ||||
|  | ||||
| // New returns a new parsing state. | ||||
| func New() *Parser { | ||||
| 	return &Parser{summary: newSummary()} | ||||
| } | ||||
|  | ||||
| // Parse parses a log line and adds it to the summary. | ||||
| func (p *Parser) Parse(line string) { | ||||
| 	// if there was an error do not continue | ||||
| 	if p.lerr != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// chain the parser's error to the result's | ||||
| 	res, err := parse(line) | ||||
| 	if p.lines++; err != nil { | ||||
| 		p.lerr = fmt.Errorf("line #%d: %s", p.lines, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	p.summary.update(res) | ||||
| } | ||||
|  | ||||
| // Summarize summarizes the parsing results. | ||||
| // Only use it after the parsing is done. | ||||
| func (p *Parser) Summarize() *Summary { | ||||
| 	return p.summary | ||||
| } | ||||
|  | ||||
| // Err returns the last error encountered | ||||
| func (p *Parser) Err() error { | ||||
| 	return p.lerr | ||||
| } | ||||
							
								
								
									
										55
									
								
								interfaces/logparser-testing/report/parser_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								interfaces/logparser-testing/report/parser_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| package report_test | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/inancgumus/learngo/28-methods/logparser-testing/report" | ||||
| ) | ||||
|  | ||||
| func newParser(lines string) *report.Parser { | ||||
| 	p := report.New() | ||||
| 	p.Parse(lines) | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| func TestParserLineErrs(t *testing.T) { | ||||
| 	p := newParser("a.com 1 2") | ||||
| 	p.Parse("b.com -1 -1") | ||||
|  | ||||
| 	want := "#2" | ||||
| 	err := p.Err().Error() | ||||
|  | ||||
| 	if !strings.Contains(err, want) { | ||||
| 		t.Errorf("want: %q; got: %q", want, err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestParserStopsOnErr(t *testing.T) { | ||||
| 	p := newParser("a.com 10 20") | ||||
| 	p.Parse("b.com -1 -1") | ||||
| 	p.Parse("neverparses.com 30 40") | ||||
|  | ||||
| 	s := p.Summarize() | ||||
| 	if want, got := 10, s.Total().Visits; want != got { | ||||
| 		t.Errorf("want: %d; got: %d", want, got) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestParserIncorrectFields(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		in, name string | ||||
| 	}{ | ||||
| 		{"a.com", "missing fields"}, | ||||
| 		{"a.com -1 2", "incorrect visits"}, | ||||
| 		{"a.com 1 -1", "incorrect time spent"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			if p := newParser(tt.in); p.Err() == nil { | ||||
| 				t.Errorf("in: %q; got: nil err", tt.in) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										60
									
								
								interfaces/logparser-testing/report/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								interfaces/logparser-testing/report/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| // 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 report | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // always put all the related things together as in here | ||||
|  | ||||
| // Result stores metrics for a domain | ||||
| // it uses the value mechanics, | ||||
| // because it doesn't have to update anything | ||||
| type Result struct { | ||||
| 	Domain    string `json:"domain"` | ||||
| 	Visits    int    `json:"visits"` | ||||
| 	TimeSpent int    `json:"time_spent"` | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // add adds the metrics of another Result to itself and returns a new Result | ||||
| func (r Result) add(other Result) Result { | ||||
| 	return Result{ | ||||
| 		Domain:    r.Domain, | ||||
| 		Visits:    r.Visits + other.Visits, | ||||
| 		TimeSpent: r.TimeSpent + other.TimeSpent, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // parse parses a single log line | ||||
| func parse(line string) (r Result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != 3 { | ||||
| 		return r, fmt.Errorf("missing fields: %v", fields) | ||||
| 	} | ||||
|  | ||||
| 	f := new(field) | ||||
| 	r.Domain = fields[0] | ||||
| 	r.Visits = f.atoi("visits", fields[1]) | ||||
| 	r.TimeSpent = f.atoi("time spent", fields[2]) | ||||
| 	return r, f.err | ||||
| } | ||||
|  | ||||
| // field helps for field parsing | ||||
| type field struct{ err error } | ||||
|  | ||||
| func (f *field) atoi(name, val string) int { | ||||
| 	n, err := strconv.Atoi(val) | ||||
| 	if n < 0 || err != nil { | ||||
| 		f.err = fmt.Errorf("incorrect %s: %q", name, val) | ||||
| 	} | ||||
| 	return n | ||||
| } | ||||
							
								
								
									
										86
									
								
								interfaces/logparser-testing/report/summary.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								interfaces/logparser-testing/report/summary.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // 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 report | ||||
|  | ||||
| import ( | ||||
| 	"sort" | ||||
| ) | ||||
|  | ||||
| // Summary aggregates the parsing results | ||||
| type Summary struct { | ||||
| 	sum     map[string]Result // metrics per domain | ||||
| 	domains []string          // unique domain names | ||||
| 	total   Result            // total visits for all domains | ||||
| } | ||||
|  | ||||
| // newSummary constructs and initializes a new summary | ||||
| // You can't use its methods without pointer mechanics | ||||
| func newSummary() *Summary { | ||||
| 	return &Summary{sum: make(map[string]Result)} | ||||
| } | ||||
|  | ||||
| // Update updates the report for the given parsing result | ||||
| func (s *Summary) update(r Result) { | ||||
| 	domain := r.Domain | ||||
| 	if _, ok := s.sum[domain]; !ok { | ||||
| 		s.domains = append(s.domains, domain) | ||||
| 	} | ||||
|  | ||||
| 	// let the result handle the addition | ||||
| 	// this allows us to manage the result in once place | ||||
| 	// and this way it becomes easily extendable | ||||
| 	s.total = s.total.add(r) | ||||
| 	s.sum[domain] = r.add(s.sum[domain]) | ||||
| } | ||||
|  | ||||
| // Iterator returns `next()` to detect when the iteration ends, | ||||
| // and a `cur()` to return the current result. | ||||
| // iterator iterates sorted by domains. | ||||
| func (s *Summary) Iterator() (next func() bool, cur func() Result) { | ||||
| 	sort.Strings(s.domains) | ||||
|  | ||||
| 	// remember the last iterated result | ||||
| 	var last int | ||||
|  | ||||
| 	next = func() bool { | ||||
| 		defer func() { last++ }() | ||||
| 		return len(s.domains) > last | ||||
| 	} | ||||
|  | ||||
| 	cur = func() Result { | ||||
| 		// returns a copy so the caller cannot change it | ||||
| 		name := s.domains[last-1] | ||||
| 		return s.sum[name] | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Total returns the total metrics | ||||
| func (s *Summary) Total() Result { | ||||
| 	return s.total | ||||
| } | ||||
|  | ||||
| // For the interfaces section | ||||
| // | ||||
| // MarshalJSON marshals a report to JSON | ||||
| // Alternative: unexported embedding | ||||
| // func (s *Summary) MarshalJSON() ([]byte, error) { | ||||
| // 	type total struct { | ||||
| // 		*Result | ||||
| // 		IgnoreDomain *string `json:"domain,omitempty"` | ||||
| // 	} | ||||
|  | ||||
| // 	return json.Marshal(struct { | ||||
| // 		Sum     map[string]Result `json:"summary"` | ||||
| // 		Domains []string          `json:"domains"` | ||||
| // 		Total   total             `json:"total"` | ||||
| // 	}{ | ||||
| // 		Sum: s.sum, Domains: s.domains, Total: total{Result: &s.total}, | ||||
| // 	}) | ||||
| // } | ||||
							
								
								
									
										44
									
								
								interfaces/logparser-testing/report/summary_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								interfaces/logparser-testing/report/summary_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| package report_test | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/inancgumus/learngo/28-methods/logparser-testing/report" | ||||
| ) | ||||
|  | ||||
| func TestSummaryTotal(t *testing.T) { | ||||
| 	p := newParser("a.com 1 2") | ||||
| 	p.Parse("b.com 3 4") | ||||
|  | ||||
| 	s := p.Summarize() | ||||
|  | ||||
| 	want := report.Result{Domain: "", Visits: 4, TimeSpent: 6} | ||||
| 	if got := s.Total(); want != got { | ||||
| 		t.Errorf("want: %+v; got: %+v", want, got) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSummaryIterator(t *testing.T) { | ||||
| 	p := newParser("a.com 1 2") | ||||
| 	p.Parse("a.com 3 4") | ||||
| 	p.Parse("b.com 5 6") | ||||
|  | ||||
| 	s := p.Summarize() | ||||
| 	next, cur := s.Iterator() | ||||
|  | ||||
| 	wants := []report.Result{ | ||||
| 		{Domain: "a.com", Visits: 4, TimeSpent: 6}, | ||||
| 		{Domain: "b.com", Visits: 5, TimeSpent: 6}, | ||||
| 	} | ||||
|  | ||||
| 	for _, want := range wants { | ||||
| 		t.Run(want.Domain, func(t *testing.T) { | ||||
| 			if got := next(); !got { | ||||
| 				t.Errorf("next(): want: %t; got: %t", true, got) | ||||
| 			} | ||||
| 			if got := cur(); want != got { | ||||
| 				t.Errorf("cur(): want: %+v; got: %+v", want, got) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										73
									
								
								interfaces/logparser-testing/summarize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								interfaces/logparser-testing/summarize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // 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" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/inancgumus/learngo/27-interfaces/logparser-testing/report" | ||||
| ) | ||||
|  | ||||
| // summarize prints the parsing results. | ||||
| // | ||||
| // it prints the errors and returns if there are any. | ||||
| // | ||||
| // --json flag encodes to json and prints. | ||||
| func summarize(sum *report.Summary, errors ...error) { | ||||
| 	if errs(errors...) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if args := os.Args[1:]; len(args) == 1 && args[0] == "--json" { | ||||
| 		encode(sum) | ||||
| 		return | ||||
| 	} | ||||
| 	stdout(sum) | ||||
| } | ||||
|  | ||||
| // encodes the summary to json | ||||
| func encode(sum *report.Summary) { | ||||
| 	out, err := json.MarshalIndent(sum, "", "\t") | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	os.Stdout.Write(out) | ||||
| } | ||||
|  | ||||
| // prints the summary to standard out | ||||
| func stdout(sum *report.Summary) { | ||||
| 	const ( | ||||
| 		head = "%-30s %10s %20s\n" | ||||
| 		val  = "%-30s %10d %20d\n" | ||||
| 	) | ||||
|  | ||||
| 	fmt.Printf(head, "DOMAIN", "VISITS", "TIME SPENT") | ||||
| 	fmt.Println(strings.Repeat("-", 65)) | ||||
|  | ||||
| 	for next, cur := sum.Iterator(); next(); { | ||||
| 		r := cur() | ||||
| 		fmt.Printf(val, r.Domain, r.Visits, r.TimeSpent) | ||||
| 	} | ||||
|  | ||||
| 	t := sum.Total() | ||||
| 	fmt.Printf("\n"+val, "TOTAL", t.Visits, t.TimeSpent) | ||||
| } | ||||
|  | ||||
| // this variadic func simplifies the multiple error handling | ||||
| func errs(errs ...error) (wasErr bool) { | ||||
| 	for _, err := range errs { | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("> Err: %s\n", err) | ||||
| 			wasErr = true | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										33
									
								
								interfaces/logs/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								interfaces/logs/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| SHELL := /bin/bash | ||||
| LINES = $$(wc -l log.txt | cut -f1 -d' ') | ||||
| ECHO_LINES = echo -e ">> log.txt has $(LINES) lines" | ||||
|  | ||||
| n ?= 18 | ||||
| load: restore | ||||
| 	@echo "enlarging the file with itself, please wait..."	 | ||||
|  | ||||
| 	@for i in {1..$(n)}; do \ | ||||
| 		awk 1 log.txt log.txt > log_.txt; \ | ||||
| 		mv log_.txt log.txt; \ | ||||
| 		rm -f log_.txt; \ | ||||
| 	done | ||||
| 	 | ||||
| 	@$(ECHO_LINES) | ||||
|  | ||||
| restore: | ||||
| 	@echo "restoring the file..." | ||||
| 	# @git checkout log.txt | ||||
| 	@$(ECHO_LINES) | ||||
|  | ||||
| multiply: remove | ||||
| 	@echo "creating 20 log files..." | ||||
| 	@for i in {1..20}; do \ | ||||
| 		echo log$${i}.txt; \ | ||||
| 		cp log.txt log$${i}.txt; \ | ||||
| 	done | ||||
|  | ||||
| remove: | ||||
| 	rm -f log{1..20}.txt | ||||
|  | ||||
| lines: | ||||
| 	@$(ECHO_LINES) | ||||
							
								
								
									
										18
									
								
								interfaces/logs/log.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								interfaces/logs/log.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| [ | ||||
|     {"domain": "learngoprogramming.com", "page": "/", "visits": 10, "uniques": 5}, | ||||
|     {"domain": "learngoprogramming.com", "page": "/courses", "visits": 15, "uniques": 10}, | ||||
|     {"domain": "learngoprogramming.com", "page": "/courses", "visits": 10, "uniques": 5}, | ||||
|     {"domain": "learngoprogramming.com", "page": "/articles", "visits": 20, "uniques": 15}, | ||||
|     {"domain": "learngoprogramming.com", "page": "/articles", "visits": 5, "uniques": 2}, | ||||
|     {"domain": "golang.org", "page": "/", "visits": 40, "uniques": 20}, | ||||
|     {"domain": "golang.org", "page": "/", "visits": 20, "uniques": 10}, | ||||
|     {"domain": "golang.org", "page": "/blog", "visits": 45, "uniques": 25}, | ||||
|     {"domain": "golang.org", "page": "/blog", "visits": 15, "uniques": 5}, | ||||
|     {"domain": "blog.golang.org", "page": "/courses", "visits": 60, "uniques": 30}, | ||||
|     {"domain": "blog.golang.org", "page": "/courses", "visits": 30, "uniques": 20}, | ||||
|     {"domain": "blog.golang.org", "page": "/updates", "visits": 20, "uniques": 10}, | ||||
|     {"domain": "blog.golang.org", "page": "/reference", "visits": 65, "uniques": 35}, | ||||
|     {"domain": "blog.golang.org", "page": "/reference", "visits": 15, "uniques": 5}, | ||||
|     {"domain": "inanc.io", "page": "/about", "visits": 30, "uniques": 15}, | ||||
|     {"domain": "inanc.io", "page": "/about","visits": 70, "uniques": 35} | ||||
| ] | ||||
							
								
								
									
										16
									
								
								interfaces/logs/log.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								interfaces/logs/log.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| learngoprogramming.com / 10 5 | ||||
| learngoprogramming.com /courses 15 10 | ||||
| learngoprogramming.com /courses 10 5 | ||||
| learngoprogramming.com /articles 20 15 | ||||
| learngoprogramming.com /articles 5 2 | ||||
| golang.org / 40 20 | ||||
| golang.org / 20 10 | ||||
| golang.org /blog 45 25 | ||||
| golang.org /blog 15 5 | ||||
| blog.golang.org /courses 60 30 | ||||
| blog.golang.org /courses 30 20 | ||||
| blog.golang.org /updates 20 10 | ||||
| blog.golang.org /reference 65 35 | ||||
| blog.golang.org /reference 15 5 | ||||
| inanc.io /about 30 15 | ||||
| inanc.io /about 70 35 | ||||
							
								
								
									
										16
									
								
								interfaces/logs/log_err_missing.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								interfaces/logs/log_err_missing.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| learngoprogramming.com / 10 5 | ||||
| learngoprogramming.com /courses 15 10 | ||||
| learngoprogramming.com /courses 10 5 | ||||
| learngoprogramming.com /articles 20 15 | ||||
| learngoprogramming.com /articles 5 2 | ||||
| golang.org / 40 20 | ||||
| golang.org / 20 10 | ||||
| golang.org /blog 45 25 | ||||
| golang.org /blog 15 5 | ||||
| blog.golang.org /updates | ||||
| blog.golang.org /updates 30 20 | ||||
| blog.golang.org /updates 20 10 | ||||
| blog.golang.org /reference 65 35 | ||||
| blog.golang.org /reference 15 5 | ||||
| inanc.io /about 30 15 | ||||
| inanc.io /about 70 35 | ||||
							
								
								
									
										16
									
								
								interfaces/logs/log_err_negative.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								interfaces/logs/log_err_negative.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| learngoprogramming.com / 10 5 | ||||
| learngoprogramming.com /courses 15 10 | ||||
| learngoprogramming.com /courses 10 5 | ||||
| learngoprogramming.com /articles 20 15 | ||||
| learngoprogramming.com /articles 5 2 | ||||
| golang.org / 40 20 | ||||
| golang.org / 20 10 | ||||
| golang.org /blog 45 -250 | ||||
| golang.org /blog 15 5 | ||||
| blog.golang.org /updates 60 30 | ||||
| blog.golang.org /updates 30 20 | ||||
| blog.golang.org /updates 20 10 | ||||
| blog.golang.org /reference 65 35 | ||||
| blog.golang.org /reference 15 5 | ||||
| inanc.io /about 30 15 | ||||
| inanc.io /about 70 35 | ||||
							
								
								
									
										16
									
								
								interfaces/logs/log_err_str.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								interfaces/logs/log_err_str.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| learngoprogramming.com / 10 5 | ||||
| learngoprogramming.com /courses 15 10 | ||||
| learngoprogramming.com /courses 10 5 | ||||
| learngoprogramming.com /articles 20 15 | ||||
| learngoprogramming.com /articles 5 2 | ||||
| golang.org / 40 TWENTY | ||||
| golang.org / 20 10 | ||||
| golang.org /blog 45 25 | ||||
| golang.org /blog 15 5 | ||||
| blog.golang.org /updates 60 30 | ||||
| blog.golang.org /updates 30 20 | ||||
| blog.golang.org /updates 20 10 | ||||
| blog.golang.org /reference 65 35 | ||||
| blog.golang.org /reference 15 5 | ||||
| inanc.io /about 30 15 | ||||
| inanc.io /about 70 35 | ||||
							
								
								
									
										36
									
								
								interfaces/notes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								interfaces/notes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| # Func Values | ||||
|  | ||||
| + Please feel free to put any function that match this signature here and it will be called each time my function is called. | ||||
|  | ||||
| + They are extremely useful when wanting to declare a block of code that you want to pass around. | ||||
|  | ||||
| + They allow functions to be passed as parameters. | ||||
|  | ||||
| + They can be chained together | ||||
|  | ||||
| # Interfaces vs Func Values | ||||
|  | ||||
| + Use interfaces when a method needs to belong to an object (that needs to operate on the value of a type: io.Reader, sort.Interface, etc). | ||||
|  | ||||
| + Use func values when a function doesn't need to belong to an object. For simple, not-data oriented jobs: Filtering, splitting, mapping etc. | ||||
|  | ||||
| + An implementation of the interface means that the behaviour is intrinsic to the implementing object. The behaviour does not change based on the caller or the circumstances of the call. | ||||
|  | ||||
|   + The func value is saying that the operation is not intrinsic to the object but based on context or the caller to define. | ||||
|  | ||||
| + An interface is a language feature representing some contract: "I hereby guarantee I make the following methods available". | ||||
|  | ||||
| + The benefit of using interfaces is that you give the type a name (you would get that with records too) and you are more clearly expressing your intention (other components can implement the interface). | ||||
|  | ||||
| + Use interface values when: | ||||
|   + Well known interfaces already exist, e.g. io.Reader | ||||
|   + More than one behavior required | ||||
|   + Typically stateful | ||||
|   + Implementations non-trivial | ||||
|  | ||||
|   + Use function values when: | ||||
|     + Only one behavior | ||||
|     + Typically stateless | ||||
|     + In-line implementations typical | ||||
|  | ||||
|   + http://go-talks.appspot.com/github.com/ChrisHines/talks/non-orthogonal-choices-in-go/non-orthogonal-choices-in-go.slide#1 | ||||
							
								
								
									
										5
									
								
								interfaces/oop/final/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								interfaces/oop/final/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| r:  | ||||
| 	go run . < ../../logs/log.txt | ||||
|  | ||||
| t:  | ||||
| 	time go run . < ../../logs/log.txt | ||||
							
								
								
									
										55
									
								
								interfaces/oop/final/analysis.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								interfaces/oop/final/analysis.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| // 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 "sort" | ||||
|  | ||||
| type ( | ||||
| 	groupFunc  func(result) string | ||||
| 	filterFunc func(result) bool | ||||
| ) | ||||
|  | ||||
| type analysis struct { | ||||
| 	sum      map[string]result // metrics per domain | ||||
| 	keys     []string          // unique keys | ||||
| 	groupKey groupFunc | ||||
| 	filter   filterFunc | ||||
| } | ||||
|  | ||||
| func newAnalysis() *analysis { | ||||
| 	return &analysis{ | ||||
| 		sum:      make(map[string]result), | ||||
| 		groupKey: domainGrouper, | ||||
| 		filter:   noopFilter, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (a *analysis) groupBy(g groupFunc)   { a.groupKey = g } | ||||
| func (a *analysis) filterBy(f filterFunc) { a.filter = f } | ||||
|  | ||||
| func (a *analysis) analyse(r result) { | ||||
| 	if !a.filter(r) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	key := a.groupKey(r) | ||||
| 	if _, ok := a.sum[key]; !ok { | ||||
| 		a.keys = append(a.keys, key) | ||||
| 	} | ||||
|  | ||||
| 	a.sum[key] = r.add(a.sum[key]) | ||||
| } | ||||
|  | ||||
| // each analysed result will be sent by the given func | ||||
| func (a *analysis) each(f func(r result)) { | ||||
| 	sort.Strings(a.keys) | ||||
|  | ||||
| 	for _, domain := range a.keys { | ||||
| 		f(a.sum[domain]) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										42
									
								
								interfaces/oop/final/chartsummary.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								interfaces/oop/final/chartsummary.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"strconv" | ||||
|  | ||||
| 	c "github.com/wcharczuk/go-chart" | ||||
| ) | ||||
|  | ||||
| type chartSummary struct { | ||||
| 	title         string | ||||
| 	width, height int | ||||
| } | ||||
|  | ||||
| func (s *chartSummary) summarize(results iterator) error { | ||||
| 	w := os.Stdout | ||||
|  | ||||
| 	donut := c.DonutChart{ | ||||
| 		Title: s.title, | ||||
| 		TitleStyle: c.Style{ | ||||
| 			FontSize:  35, | ||||
| 			Show:      true, | ||||
| 			FontColor: c.ColorAlternateGreen, | ||||
| 		}, | ||||
| 		Width:  s.width, | ||||
| 		Height: s.height, | ||||
| 	} | ||||
|  | ||||
| 	results.each(func(r result) { | ||||
| 		v := c.Value{ | ||||
| 			Label: r.domain + r.page + ": " + strconv.Itoa(r.visits), | ||||
| 			Value: float64(r.visits), | ||||
| 			Style: c.Style{ | ||||
| 				FontSize: 14, | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		donut.Values = append(donut.Values, v) | ||||
| 	}) | ||||
|  | ||||
| 	return donut.Render(c.SVG, w) | ||||
| } | ||||
							
								
								
									
										34
									
								
								interfaces/oop/final/filters.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								interfaces/oop/final/filters.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| package main | ||||
|  | ||||
| import "strings" | ||||
|  | ||||
| func noopFilter(r result) bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func notUsing(filter filterFunc) filterFunc { | ||||
| 	return func(r result) bool { | ||||
| 		return !filter(r) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func domainExtFilter(domains ...string) filterFunc { | ||||
| 	return func(r result) bool { | ||||
| 		for _, domain := range domains { | ||||
| 			if strings.HasSuffix(r.domain, "."+domain) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func domainFilter(domain string) filterFunc { | ||||
| 	return func(r result) bool { | ||||
| 		return strings.Contains(r.domain, domain) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func orgDomainsFilter(r result) bool { | ||||
| 	return strings.HasSuffix(r.domain, ".org") | ||||
| } | ||||
							
								
								
									
										13
									
								
								interfaces/oop/final/groupers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								interfaces/oop/final/groupers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| package main | ||||
|  | ||||
| // domainGrouper groups by domain. | ||||
| // but it keeps the other fields. | ||||
| // for example: it returns pages as well, but you shouldn't use them. | ||||
| // exercise: write a function that erases the unnecessary data. | ||||
| func domainGrouper(r result) string { | ||||
| 	return r.domain | ||||
| } | ||||
|  | ||||
| func pageGrouper(r result) string { | ||||
| 	return r.domain + r.page | ||||
| } | ||||
							
								
								
									
										69
									
								
								interfaces/oop/final/jsonparser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								interfaces/oop/final/jsonparser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| // For more tutorials: https://bp.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" | ||||
| 	"io/ioutil" | ||||
| ) | ||||
|  | ||||
| type jsonParser struct { | ||||
| 	r      io.Reader | ||||
| 	update func(r result) | ||||
| } | ||||
|  | ||||
| func newJSONParser(r io.Reader) *jsonParser { | ||||
| 	return &jsonParser{ | ||||
| 		r:      r, | ||||
| 		update: func(result) {}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *jsonParser) parse() error { | ||||
| 	defer readClose(p.r) | ||||
|  | ||||
| 	bytes, err := ioutil.ReadAll(p.r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return p.parseJSON(bytes) | ||||
| } | ||||
|  | ||||
| func (p *jsonParser) parseJSON(bytes []byte) error { | ||||
| 	var rs []struct { | ||||
| 		Domain  string | ||||
| 		Page    string | ||||
| 		Visits  int | ||||
| 		Uniques int | ||||
| 	} | ||||
|  | ||||
| 	if err := json.Unmarshal(bytes, &rs); err != nil { | ||||
| 		if serr, ok := err.(*json.SyntaxError); ok { | ||||
| 			return fmt.Errorf("%v %q", serr, bytes[:serr.Offset]) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, r := range rs { | ||||
| 		p.update(result{ | ||||
| 			domain:  r.Domain, | ||||
| 			page:    r.Page, | ||||
| 			visits:  r.Visits, | ||||
| 			uniques: r.Uniques, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *jsonParser) notify(f func(r result)) { | ||||
| 	p.update = f | ||||
| } | ||||
							
								
								
									
										39
									
								
								interfaces/oop/final/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								interfaces/oop/final/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| // 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 ( | ||||
| 	"log" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	an := newAnalysis() | ||||
| 	an.filterBy(notUsing(domainExtFilter("io", "com"))) | ||||
| 	an.groupBy(domainGrouper) | ||||
|  | ||||
| 	// pars, err := parseTextFile("log.txt") | ||||
| 	// if err != nil { | ||||
| 	// 	log.Fatalln(err) | ||||
| 	// } | ||||
|  | ||||
| 	err := report(an, newTextParser(os.Stdin), summarizeFunc(textSummary)) | ||||
| 	// err := report(an, newJSONParser(os.Stdin), newTextSummary()) | ||||
|  | ||||
| 	// chart := &chartSummary{ | ||||
| 	// 	title:  "visits per domain", | ||||
| 	// 	width:  1920, | ||||
| 	// 	height: 800, | ||||
| 	// } | ||||
|  | ||||
| 	// err := report(an, newTextParser(os.Stdin), chart) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										18
									
								
								interfaces/oop/final/readclose.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								interfaces/oop/final/readclose.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| func readClose(r io.Reader) { | ||||
| 	if rc, ok := r.(io.Closer); ok { | ||||
| 		rc.Close() | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										40
									
								
								interfaces/oop/final/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								interfaces/oop/final/report.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 | ||||
|  | ||||
| type notifier interface { | ||||
| 	notify(func(r result)) | ||||
| } | ||||
|  | ||||
| type parser interface { | ||||
| 	parse() error | ||||
| 	notifier | ||||
| } | ||||
|  | ||||
| type iterator interface { | ||||
| 	each(func(result)) | ||||
| } | ||||
|  | ||||
| type analyser interface { | ||||
| 	analyse(result) | ||||
| 	iterator | ||||
| } | ||||
|  | ||||
| type summarizer interface { | ||||
| 	summarize(iterator) error | ||||
| } | ||||
|  | ||||
| func report(a analyser, p parser, s summarizer) error { | ||||
| 	p.notify(a.analyse) | ||||
|  | ||||
| 	if err := p.parse(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return s.summarize(a) | ||||
| } | ||||
							
								
								
									
										14
									
								
								interfaces/oop/final/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								interfaces/oop/final/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| package main | ||||
|  | ||||
| type result struct { | ||||
| 	domain  string | ||||
| 	page    string | ||||
| 	visits  int | ||||
| 	uniques int | ||||
| } | ||||
|  | ||||
| func (r result) add(other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										86
									
								
								interfaces/oop/final/textparser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								interfaces/oop/final/textparser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| // For more tutorials: https://bp.learngoprogramming.com | ||||
| // | ||||
| // Copyright © 2018 Inanc Gumus | ||||
| // Learn Go Programming Course | ||||
| // License: https://creativecommons.org/licenses/by-nc-sa/4.0/ | ||||
| // | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| type textParser struct { | ||||
| 	r      io.Reader | ||||
| 	update func(r result) | ||||
| } | ||||
|  | ||||
| func newTextParser(r io.Reader) *textParser { | ||||
| 	return &textParser{ | ||||
| 		r:      r, | ||||
| 		update: func(result) {}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func parseTextFile(path string) (*textParser, error) { | ||||
| 	f, err := os.Open(path) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return newTextParser(f), nil | ||||
| } | ||||
|  | ||||
| func (p *textParser) notify(f func(r result)) { | ||||
| 	p.update = f | ||||
| } | ||||
|  | ||||
| func (p *textParser) parse() error { | ||||
| 	defer readClose(p.r) | ||||
|  | ||||
| 	var ( | ||||
| 		l  = 1 | ||||
| 		in = bufio.NewScanner(p.r) | ||||
| 	) | ||||
|  | ||||
| 	for in.Scan() { | ||||
| 		r, err := p.parseFields(in.Text()) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
|  | ||||
| 		p.update(r) | ||||
| 		l++ | ||||
| 	} | ||||
|  | ||||
| 	return in.Err() | ||||
| } | ||||
|  | ||||
| func (p *textParser) parseFields(s string) (r result, err error) { | ||||
| 	fields := strings.Fields(s) | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong number of fields %q", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain, r.page = fields[0], fields[1] | ||||
|  | ||||
| 	r.visits, err = strconv.Atoi(fields[2]) | ||||
| 	if err != nil || r.visits < 0 { | ||||
| 		return r, fmt.Errorf("wrong input %q", fields[2]) | ||||
| 	} | ||||
|  | ||||
| 	r.uniques, err = strconv.Atoi(fields[3]) | ||||
| 	if err != nil || r.uniques < 0 { | ||||
| 		return r, fmt.Errorf("wrong input %q", fields[3]) | ||||
| 	} | ||||
|  | ||||
| 	return r, nil | ||||
| } | ||||
							
								
								
									
										56
									
								
								interfaces/oop/final/textsummary.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								interfaces/oop/final/textsummary.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| // 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" | ||||
| 	"os" | ||||
| 	"text/tabwriter" | ||||
| ) | ||||
|  | ||||
| // TODO: make this configurable? or exercise? | ||||
| const ( | ||||
| 	minWidth = 0 | ||||
| 	tabWidth = 4 | ||||
| 	padding  = 4 | ||||
| 	flags    = 0 | ||||
| ) | ||||
|  | ||||
| type summarizeFunc func(iterator) error | ||||
|  | ||||
| func (f summarizeFunc) summarize(results iterator) error { | ||||
| 	return f(results) | ||||
| } | ||||
|  | ||||
| // type textSummary struct{} | ||||
|  | ||||
| // func newTextSummary() *textSummary { | ||||
| // 	return new(textSummary) | ||||
| // } | ||||
|  | ||||
| // func (s *textSummary) summarize(results iterator) error { | ||||
| func textSummary(results iterator) error { | ||||
| 	w := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, ' ', flags) | ||||
|  | ||||
| 	write := fmt.Fprintf | ||||
|  | ||||
| 	write(w, "DOMAINS\tPAGES\tVISITS\tUNIQUES\n") | ||||
| 	write(w, "-------\t-----\t------\t-------\n") | ||||
|  | ||||
| 	var total result | ||||
| 	results.each(func(r result) { | ||||
| 		total = total.add(r) | ||||
|  | ||||
| 		write(w, "%s\t%s\t%d\t%d\n", r.domain, r.page, r.visits, r.uniques) | ||||
| 	}) | ||||
|  | ||||
| 	write(w, "\t\t\t\n") | ||||
| 	write(w, "%s\t%s\t%d\t%d\n", "TOTAL", "", total.visits, total.uniques) | ||||
|  | ||||
| 	return w.Flush() | ||||
| } | ||||
							
								
								
									
										34
									
								
								interfaces/refactor-notes/refactor-00/changes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								interfaces/refactor-notes/refactor-00/changes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| ## CHANGES | ||||
|  | ||||
| ### PROBLEM | ||||
| + adding new fields makes the code complex | ||||
| + needs to update: `result`, `parser`, `summarizer` | ||||
| + needs to add new fields to `parser`: `totalVisits` + `totalUniques` | ||||
| + in `parse()`: repeating line errors | ||||
|   + if we parsing out of it we'd need to have *parser — superfluous | ||||
|  | ||||
| ### SOLUTION | ||||
| + move all the result related logic to result.go | ||||
|  | ||||
| + move `parser.go/result` -> `result.go` | ||||
|   + move `parser.go/parsing` logic -> `result.go` | ||||
|  | ||||
| + add `addResult` -> `result.go` | ||||
|   + remove `parser struct`'s: `totalVisits`, `totalUniques` | ||||
|   + change `update()`'s last line: `p.sum[r.domain] = addResult` | ||||
|  | ||||
| + remove `(line #d)` errors from `result.go` | ||||
|   + add: `return r, err` — named params are error prone | ||||
|   + always check for the error first | ||||
|     + `if r.visits < 0 || err != nil` -> `if err != nil || r.visits < 0` | ||||
|  | ||||
| + `parser.go`: check the `parseFields()`: | ||||
|     ```golang | ||||
|     r, err := parseFields(line) | ||||
|     if err != nil { | ||||
|         p.lerr = fmt.Errorf("line %d: %v", p.lines, err) | ||||
|     }``` | ||||
|  | ||||
| + - `parser.go` and `summarize.go` | ||||
|   - remove `total int` | ||||
|   - let `summarize()` calculate the totals | ||||
							
								
								
									
										36
									
								
								interfaces/refactor-notes/refactor-00/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								interfaces/refactor-notes/refactor-00/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| // 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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	p := newParser() | ||||
|  | ||||
| 	in := bufio.NewScanner(os.Stdin) | ||||
| 	for in.Scan() { | ||||
| 		parsed := parse(p, in.Text()) | ||||
| 		update(p, parsed) | ||||
| 	} | ||||
|  | ||||
| 	summarize(p) | ||||
| 	dumpErrs([]error{in.Err(), err(p)}) | ||||
| } | ||||
|  | ||||
| // dumpErrs simplifies handling multiple errors | ||||
| func dumpErrs(errs []error) { | ||||
| 	for _, err := range errs { | ||||
| 		if err != nil { | ||||
| 			fmt.Println("> Err:", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										74
									
								
								interfaces/refactor-notes/refactor-00/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								interfaces/refactor-notes/refactor-00/parser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // parser keeps track of the parsing | ||||
| type parser struct { | ||||
| 	sum     map[string]result // metrics per domain | ||||
| 	domains []string          // unique domain names | ||||
| 	lines   int               // number of parsed lines (for the error messages) | ||||
| 	lerr    error             // the last error occurred | ||||
|  | ||||
| 	// totalVisits  int               // total visits for all domains | ||||
| 	// totalUniques int               // total uniques for all domains | ||||
| } | ||||
|  | ||||
| // newParser constructs, initializes and returns a new parser | ||||
| func newParser() *parser { | ||||
| 	return &parser{sum: make(map[string]result)} | ||||
| } | ||||
|  | ||||
| // parse a log line and return the result | ||||
| func parse(p *parser, line string) (r result) { | ||||
| 	if p.lerr != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	p.lines++ | ||||
|  | ||||
| 	r, err := parseResult(line) | ||||
| 	if err != nil { | ||||
| 		p.lerr = fmt.Errorf("line %d: %v", p.lines, err) | ||||
| 	} | ||||
|  | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| // update the parsing results | ||||
| func update(p *parser, r result) { | ||||
| 	if p.lerr != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Collect the unique domains | ||||
| 	cur, ok := p.sum[r.domain] | ||||
| 	if !ok { | ||||
| 		p.domains = append(p.domains, r.domain) | ||||
| 	} | ||||
|  | ||||
| 	// Keep track of total and per domain visits | ||||
| 	// p.totalVisits += r.visits | ||||
| 	// p.totalUniques += r.uniques | ||||
|  | ||||
| 	// create and assign a new copy of `visit` | ||||
| 	// p.sum[r.domain] = result{ | ||||
| 	// 	domain:  r.domain, | ||||
| 	// 	visits:  r.visits + cur.visits, | ||||
| 	// 	uniques: r.uniques + cur.uniques, | ||||
| 	// } | ||||
| 	p.sum[r.domain] = addResult(r, cur) | ||||
| } | ||||
|  | ||||
| // err returns the last error encountered | ||||
| func err(p *parser) error { | ||||
| 	return p.lerr | ||||
| } | ||||
							
								
								
									
										53
									
								
								interfaces/refactor-notes/refactor-00/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								interfaces/refactor-notes/refactor-00/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain, page    string | ||||
| 	visits, uniques int | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // parseResult from a log line | ||||
| func parseResult(line string) (r result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong input: %v", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.page = fields[1] | ||||
|  | ||||
| 	r.visits, err = strconv.Atoi(fields[2]) | ||||
| 	if err != nil || r.visits < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[2]) | ||||
| 	} | ||||
|  | ||||
| 	r.uniques, err = strconv.Atoi(fields[3]) | ||||
| 	if err != nil || r.uniques < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[3]) | ||||
| 	} | ||||
|  | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // addResult to another one | ||||
| func addResult(r, other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										43
									
								
								interfaces/refactor-notes/refactor-00/summarize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								interfaces/refactor-notes/refactor-00/summarize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // 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" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	//          DOMAINS PAGES VISITS UNIQUES | ||||
| 	//             ^      ^     ^    ^ | ||||
| 	//             |      |     |    | | ||||
| 	header     = "%-25s %-10s %10s %10s\n" | ||||
| 	line       = "%-25s %-10s %10d %10d\n" | ||||
| 	footer     = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES | ||||
| 	dash       = "-" | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| func summarize(p *parser) { | ||||
| 	sort.Strings(p.domains) | ||||
|  | ||||
| 	fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES") | ||||
| 	fmt.Println(strings.Repeat("-", dashLength)) | ||||
|  | ||||
| 	var total result | ||||
|  | ||||
| 	for _, domain := range p.domains { | ||||
| 		r := p.sum[domain] | ||||
| 		total = addResult(total, r) | ||||
|  | ||||
| 		fmt.Printf(line, r.domain, r.page, r.visits, r.uniques) | ||||
| 	} | ||||
| 	fmt.Printf(footer, "TOTAL", total.visits, total.uniques) | ||||
| } | ||||
							
								
								
									
										61
									
								
								interfaces/refactor-notes/refactor-01/changes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								interfaces/refactor-notes/refactor-01/changes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| ### PROBLEM | ||||
| + `main.go` (api client) does a lot of things: | ||||
|   + read the log input | ||||
|   + parse line by line | ||||
|   + updates the results | ||||
|   + display the results | ||||
|  | ||||
| + inflexible: | ||||
|   + filter by extension (can change) | ||||
|   + group by domain (can change) — group by page? | ||||
|  | ||||
| ## SOLUTION | ||||
| + hide the parsing api from the client | ||||
|  | ||||
| + move `main.go/scanner` -> `parser.go/parse()` | ||||
|   + add `main.go`: err handling from `parse()` | ||||
|  | ||||
| + `parser.go/parse()` -> return err directly | ||||
|   + remove: `if p.lerr != nil { return }` from parse() and update() | ||||
|   + remove: `dumpErrs` | ||||
|   + remove: `parser.go/err()` | ||||
|   + remove `parser.go/lerr` | ||||
|     + return `in.Err()` from `parse()` | ||||
|  | ||||
|   + remove: `p.lines++` | ||||
|     + `return r, fmt.Errorf("line %d: %v", p.lines, err)` | ||||
|     + remove: `lines int` | ||||
|     + `parse()` and `parse()` becomes: | ||||
|     ```golang | ||||
|     func parse(p *parser, line string) (result, error) { | ||||
|         return parseFields(line) | ||||
|     } | ||||
|  | ||||
|     func parse(p *parser) { | ||||
|         // ... | ||||
|         r, err := parse(p, in.Text()) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("line %d: %v", p.lines, err) | ||||
|         } | ||||
|         // ... | ||||
|     } | ||||
|     ``` | ||||
|  | ||||
|     + remove `parse()` | ||||
|     + call `parseFields` directly in `parse()`: | ||||
|     ```go | ||||
| 	var ( | ||||
| 		l  = 1 | ||||
| 		in = bufio.NewScanner(os.Stdin) | ||||
| 	) | ||||
|  | ||||
| 	for in.Scan() { | ||||
| 		r, err := parseFields(in.Text()) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
|  | ||||
| 		update(p, r) | ||||
| 		l++ | ||||
|     } | ||||
|     ``` | ||||
							
								
								
									
										23
									
								
								interfaces/refactor-notes/refactor-01/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								interfaces/refactor-notes/refactor-01/main.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" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	p := newParser() | ||||
|  | ||||
| 	if err := parse(p); err != nil { | ||||
| 		fmt.Println("> Err:", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	summarize(p) | ||||
| } | ||||
							
								
								
									
										57
									
								
								interfaces/refactor-notes/refactor-01/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								interfaces/refactor-notes/refactor-01/parser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| // 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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // parser keeps track of the parsing | ||||
| type parser struct { | ||||
| 	sum     map[string]result // metrics per domain | ||||
| 	domains []string          // unique domain names | ||||
| } | ||||
|  | ||||
| // newParser constructs, initializes and returns a new parser | ||||
| func newParser() *parser { | ||||
| 	return &parser{sum: make(map[string]result)} | ||||
| } | ||||
|  | ||||
| // parse the log lines and return results | ||||
| func parse(p *parser) error { | ||||
| 	var ( | ||||
| 		l  = 1 | ||||
| 		in = bufio.NewScanner(os.Stdin) | ||||
| 	) | ||||
|  | ||||
| 	for in.Scan() { | ||||
| 		r, err := parseResult(in.Text()) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
|  | ||||
| 		l++ | ||||
|  | ||||
| 		update(p, r) | ||||
| 	} | ||||
|  | ||||
| 	return in.Err() | ||||
| } | ||||
|  | ||||
| // update the parsing results | ||||
| func update(p *parser, r result) { | ||||
| 	// Collect the unique domains | ||||
| 	if _, ok := p.sum[r.domain]; !ok { | ||||
| 		p.domains = append(p.domains, r.domain) | ||||
| 	} | ||||
|  | ||||
| 	// create and assign a new copy of `visit` | ||||
| 	p.sum[r.domain] = addResult(r, p.sum[r.domain]) | ||||
| } | ||||
							
								
								
									
										53
									
								
								interfaces/refactor-notes/refactor-01/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								interfaces/refactor-notes/refactor-01/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain, page    string | ||||
| 	visits, uniques int | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // parseResult from a log line | ||||
| func parseResult(line string) (r result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong input: %v", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.page = fields[1] | ||||
|  | ||||
| 	r.visits, err = strconv.Atoi(fields[2]) | ||||
| 	if err != nil || r.visits < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[2]) | ||||
| 	} | ||||
|  | ||||
| 	r.uniques, err = strconv.Atoi(fields[3]) | ||||
| 	if err != nil || r.uniques < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[3]) | ||||
| 	} | ||||
|  | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // addResult to another one | ||||
| func addResult(r, other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										48
									
								
								interfaces/refactor-notes/refactor-01/summarize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								interfaces/refactor-notes/refactor-01/summarize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // 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" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| // + violation: accesses the parsing internals: p.domains + p.sum + p.total | ||||
| // + give it the []result only. | ||||
| // + let it calculate the total. | ||||
| const ( | ||||
|  | ||||
| 	//          DOMAINS PAGES VISITS UNIQUES | ||||
| 	//             ^      ^     ^    ^ | ||||
| 	//             |      |     |    | | ||||
| 	header     = "%-25s %-10s %10s %10s\n" | ||||
| 	line       = "%-25s %-10s %10d %10d\n" | ||||
| 	footer     = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES | ||||
| 	dash       = "-" | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| func summarize(p *parser) { | ||||
| 	sort.Strings(p.domains) | ||||
|  | ||||
| 	fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES") | ||||
| 	fmt.Println(strings.Repeat("-", dashLength)) | ||||
|  | ||||
| 	var total result | ||||
|  | ||||
| 	for _, domain := range p.domains { | ||||
| 		r := p.sum[domain] | ||||
| 		total = addResult(total, r) | ||||
|  | ||||
| 		fmt.Printf(line, r.domain, r.page, r.visits, r.uniques) | ||||
| 	} | ||||
| 	fmt.Printf(footer, "TOTAL", total.visits, total.uniques) | ||||
| } | ||||
							
								
								
									
										20
									
								
								interfaces/refactor-notes/refactor-02/changes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								interfaces/refactor-notes/refactor-02/changes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| ### PROBLEM | ||||
| + `summarize()` knows a lot about the internals of the `parser`. | ||||
|   + coupled to the `parser`. | ||||
|  | ||||
| ## SOLUTION | ||||
| + remove: `parser.go` `sum` and `domains` fields | ||||
|   + remove: `parser.go/newParser()` | ||||
|   + change: `parser.go/parse(p *parser) error` -> `parse() ([]result, error)` | ||||
|   + initialize: `sum` inside `parse()` | ||||
|   + remove: `update()` | ||||
|   + call: `sum` update in the `parse()` | ||||
|   + collect the grouped results and return them from `parser()` | ||||
|  | ||||
| + `summarize(p *parser)` -> `summarize([]result)` | ||||
| + in `summarize()` | ||||
|   + `sort.Slice` | ||||
|   + range over `[]result` | ||||
|  | ||||
| + `main.go` | ||||
|   + just: `res, err := parse()` | ||||
							
								
								
									
										22
									
								
								interfaces/refactor-notes/refactor-02/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								interfaces/refactor-notes/refactor-02/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	res, err := parse() | ||||
| 	if err != nil { | ||||
| 		fmt.Println("> Err:", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	summarize(res) | ||||
| } | ||||
							
								
								
									
										48
									
								
								interfaces/refactor-notes/refactor-02/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								interfaces/refactor-notes/refactor-02/parser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // 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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // parser keeps track of the parsing | ||||
| type parser struct { | ||||
| } | ||||
|  | ||||
| // parse the log lines and return results | ||||
| func parse() ([]result, error) { | ||||
| 	var ( | ||||
| 		l   = 1 | ||||
| 		in  = bufio.NewScanner(os.Stdin) | ||||
| 		sum = make(map[string]result) | ||||
| 	) | ||||
|  | ||||
| 	// parse the log lines | ||||
| 	for in.Scan() { | ||||
| 		r, err := parseResult(in.Text()) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
|  | ||||
| 		l++ | ||||
|  | ||||
| 		// group the log lines by domain | ||||
| 		sum[r.domain] = addResult(r, sum[r.domain]) | ||||
| 	} | ||||
|  | ||||
| 	// collect the grouped results | ||||
| 	res := make([]result, 0, len(sum)) | ||||
| 	for _, r := range sum { | ||||
| 		res = append(res, r) | ||||
| 	} | ||||
|  | ||||
| 	return res, in.Err() | ||||
| } | ||||
							
								
								
									
										53
									
								
								interfaces/refactor-notes/refactor-02/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								interfaces/refactor-notes/refactor-02/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain, page    string | ||||
| 	visits, uniques int | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // parseResult from a log line | ||||
| func parseResult(line string) (r result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong input: %v", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.page = fields[1] | ||||
|  | ||||
| 	r.visits, err = strconv.Atoi(fields[2]) | ||||
| 	if err != nil || r.visits < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[2]) | ||||
| 	} | ||||
|  | ||||
| 	r.uniques, err = strconv.Atoi(fields[3]) | ||||
| 	if err != nil || r.uniques < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[3]) | ||||
| 	} | ||||
|  | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // addResult to another one | ||||
| func addResult(r, other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										49
									
								
								interfaces/refactor-notes/refactor-02/summarize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								interfaces/refactor-notes/refactor-02/summarize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| // 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" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| // + violation: accesses the parsing internals: p.domains + p.sum + p.total | ||||
| // + give it the []result only. | ||||
| // + let it calculate the total. | ||||
| const ( | ||||
|  | ||||
| 	//          DOMAINS PAGES VISITS UNIQUES | ||||
| 	//             ^      ^     ^    ^ | ||||
| 	//             |      |     |    | | ||||
| 	header     = "%-25s %-10s %10s %10s\n" | ||||
| 	line       = "%-25s %-10s %10d %10d\n" | ||||
| 	footer     = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES | ||||
| 	dash       = "-" | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| func summarize(res []result) { | ||||
| 	// sort.Strings(p.domains) | ||||
| 	sort.Slice(res, func(i, j int) bool { | ||||
| 		return res[i].domain <= res[j].domain | ||||
| 	}) | ||||
|  | ||||
| 	fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES") | ||||
| 	fmt.Println(strings.Repeat("-", dashLength)) | ||||
|  | ||||
| 	var total result | ||||
|  | ||||
| 	for _, r := range res { | ||||
| 		total = addResult(total, r) | ||||
| 		fmt.Printf(line, r.domain, r.page, r.visits, r.uniques) | ||||
| 	} | ||||
| 	fmt.Printf(footer, "TOTAL", total.visits, total.uniques) | ||||
| } | ||||
							
								
								
									
										14
									
								
								interfaces/refactor-notes/refactor-03-deprecated/changes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								interfaces/refactor-notes/refactor-03-deprecated/changes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| ### PROBLEM | ||||
| + `parser.go/parse()` also does updating. back to square one. | ||||
|   + we need to extract the reusable behavior: scanning. | ||||
|  | ||||
| + inflexible: | ||||
|   + adding a filter is hard. needs to change the `scan()` code. | ||||
|   + adding a grouper is also hard. domain grouping is hardcoded. | ||||
|  | ||||
| ## SOLUTION | ||||
| +  | ||||
|  | ||||
| ## IDEAS: | ||||
|  | ||||
| + make domain filter accept variadic args | ||||
							
								
								
									
										36
									
								
								interfaces/refactor-notes/refactor-03-deprecated/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								interfaces/refactor-notes/refactor-03-deprecated/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| /* | ||||
| p := pipeline{ | ||||
| 	read:     textReader(os.Stdin), | ||||
| 	write:    textWriter(os.Stdout), | ||||
| 	filterBy: notUsing(domainExtFilter("io")), | ||||
| 	groupBy:  domainGrouper, | ||||
| } | ||||
|  | ||||
| if err := start(p); err != nil { | ||||
| 	fmt.Println("> Err:", err) | ||||
| } | ||||
| */ | ||||
|  | ||||
| func main() { | ||||
| 	p := newParser() | ||||
|  | ||||
| 	if err := parse(p); err != nil { | ||||
| 		fmt.Println("> Err:", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	summarize(p) | ||||
| } | ||||
							
								
								
									
										40
									
								
								interfaces/refactor-notes/refactor-03-deprecated/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								interfaces/refactor-notes/refactor-03-deprecated/parser.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 | ||||
|  | ||||
| // parser keeps track of the parsing | ||||
| type parser struct { | ||||
| 	sum     map[string]result // metrics per domain | ||||
| 	domains []string          // unique domain names | ||||
| } | ||||
|  | ||||
| // newParser constructs, initializes and returns a new parser | ||||
| func newParser() *parser { | ||||
| 	return &parser{sum: make(map[string]result)} | ||||
| } | ||||
|  | ||||
| // parse all the log lines and update the results | ||||
| func parse(p *parser) error { | ||||
| 	process := func(r result) { | ||||
| 		update(p, r) | ||||
| 	} | ||||
|  | ||||
| 	err := scan(process) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func update(p *parser, r result) { | ||||
| 	// Collect the unique domains | ||||
| 	if _, ok := p.sum[r.domain]; !ok { | ||||
| 		p.domains = append(p.domains, r.domain) | ||||
| 	} | ||||
|  | ||||
| 	// create and assign a new copy of `visit` | ||||
| 	p.sum[r.domain] = addResult(r, p.sum[r.domain]) | ||||
| } | ||||
							
								
								
									
										53
									
								
								interfaces/refactor-notes/refactor-03-deprecated/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								interfaces/refactor-notes/refactor-03-deprecated/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain, page    string | ||||
| 	visits, uniques int | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // parseResult from a log line | ||||
| func parseResult(line string) (r result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong input: %v", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.page = fields[1] | ||||
|  | ||||
| 	r.visits, err = strconv.Atoi(fields[2]) | ||||
| 	if err != nil || r.visits < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[2]) | ||||
| 	} | ||||
|  | ||||
| 	r.uniques, err = strconv.Atoi(fields[3]) | ||||
| 	if err != nil || r.uniques < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[3]) | ||||
| 	} | ||||
|  | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // addResult to another one | ||||
| func addResult(r, other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										35
									
								
								interfaces/refactor-notes/refactor-03-deprecated/scanner.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								interfaces/refactor-notes/refactor-03-deprecated/scanner.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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| type processFn func(r result) | ||||
|  | ||||
| func scan(process processFn) error { | ||||
| 	var ( | ||||
| 		l  = 1 | ||||
| 		in = bufio.NewScanner(os.Stdin) | ||||
| 	) | ||||
|  | ||||
| 	for in.Scan() { | ||||
| 		r, err := parseResult(in.Text()) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
|  | ||||
| 		l++ | ||||
|  | ||||
| 		process(r) | ||||
| 	} | ||||
| 	return in.Err() | ||||
| } | ||||
| @@ -0,0 +1,48 @@ | ||||
| // 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" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| // + violation: accesses the parsing internals: p.domains + p.sum + p.total | ||||
| // + give it the []result only. | ||||
| // + let it calculate the total. | ||||
| const ( | ||||
|  | ||||
| 	//          DOMAINS PAGES VISITS UNIQUES | ||||
| 	//             ^      ^     ^    ^ | ||||
| 	//             |      |     |    | | ||||
| 	header     = "%-25s %-10s %10s %10s\n" | ||||
| 	line       = "%-25s %-10s %10d %10d\n" | ||||
| 	footer     = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES | ||||
| 	dash       = "-" | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| func summarize(p *parser) { | ||||
| 	sort.Strings(p.domains) | ||||
|  | ||||
| 	fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES") | ||||
| 	fmt.Println(strings.Repeat("-", dashLength)) | ||||
|  | ||||
| 	var total result | ||||
|  | ||||
| 	for _, domain := range p.domains { | ||||
| 		r := p.sum[domain] | ||||
| 		total = addResult(total, r) | ||||
|  | ||||
| 		fmt.Printf(line, r.domain, r.page, r.visits, r.uniques) | ||||
| 	} | ||||
| 	fmt.Printf(footer, "TOTAL", total.visits, total.uniques) | ||||
| } | ||||
							
								
								
									
										7
									
								
								interfaces/refactor-notes/refactor-03/changes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								interfaces/refactor-notes/refactor-03/changes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| ### PROBLEM | ||||
| + ... | ||||
|  | ||||
| ## SOLUTION | ||||
| + `parser struct` -> `pipeline struct` | ||||
| + `parse()` -> `pipe(pipeline)` | ||||
|  | ||||
							
								
								
									
										29
									
								
								interfaces/refactor-notes/refactor-03/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								interfaces/refactor-notes/refactor-03/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	pipes := pipeline{ | ||||
| 		// read:     textParser(), | ||||
| 		// write:    textSummary(), | ||||
| 		// filterBy: notUsing(domainExtFilter("io", "com")), | ||||
| 		// groupBy:  domainGrouper, | ||||
| 	} | ||||
|  | ||||
| 	res, err := pipe(pipes) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("> Err:", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	summarize(res) | ||||
| } | ||||
							
								
								
									
										48
									
								
								interfaces/refactor-notes/refactor-03/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								interfaces/refactor-notes/refactor-03/parser.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // 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 ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // pipeline determines the behavior of log processing | ||||
| type pipeline struct { | ||||
| } | ||||
|  | ||||
| // pipe the log lines through funcs and produce a result | ||||
| func pipe(opts pipeline) ([]result, error) { | ||||
| 	var ( | ||||
| 		l   = 1 | ||||
| 		in  = bufio.NewScanner(os.Stdin) | ||||
| 		sum = make(map[string]result) | ||||
| 	) | ||||
|  | ||||
| 	// parse the log lines | ||||
| 	for in.Scan() { | ||||
| 		r, err := parseResult(in.Text()) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
|  | ||||
| 		l++ | ||||
|  | ||||
| 		// group the log lines by domain | ||||
| 		sum[r.domain] = addResult(r, sum[r.domain]) | ||||
| 	} | ||||
|  | ||||
| 	// collect the grouped results | ||||
| 	res := make([]result, 0, len(sum)) | ||||
| 	for _, r := range sum { | ||||
| 		res = append(res, r) | ||||
| 	} | ||||
|  | ||||
| 	return res, in.Err() | ||||
| } | ||||
							
								
								
									
										53
									
								
								interfaces/refactor-notes/refactor-03/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								interfaces/refactor-notes/refactor-03/result.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const fieldsLength = 4 | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain, page    string | ||||
| 	visits, uniques int | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // parseResult from a log line | ||||
| func parseResult(line string) (r result, err error) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != fieldsLength { | ||||
| 		return r, fmt.Errorf("wrong input: %v", fields) | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.page = fields[1] | ||||
|  | ||||
| 	r.visits, err = strconv.Atoi(fields[2]) | ||||
| 	if err != nil || r.visits < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[2]) | ||||
| 	} | ||||
|  | ||||
| 	r.uniques, err = strconv.Atoi(fields[3]) | ||||
| 	if err != nil || r.uniques < 0 { | ||||
| 		return r, fmt.Errorf("wrong input: %q", fields[3]) | ||||
| 	} | ||||
|  | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // addResult to another one | ||||
| func addResult(r, other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
							
								
								
									
										48
									
								
								interfaces/refactor-notes/refactor-03/summarize.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								interfaces/refactor-notes/refactor-03/summarize.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| // 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" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| // + violation: accesses the parsing internals: p.domains + p.sum + p.total | ||||
| // + give it the []result only. | ||||
| // + let it calculate the total. | ||||
| const ( | ||||
|  | ||||
| 	//          DOMAINS PAGES VISITS UNIQUES | ||||
| 	//             ^      ^     ^    ^ | ||||
| 	//             |      |     |    | | ||||
| 	header     = "%-25s %-10s %10s %10s\n" | ||||
| 	line       = "%-25s %-10s %10d %10d\n" | ||||
| 	footer     = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES | ||||
| 	dash       = "-" | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| // summarize summarizes and prints the parsing result | ||||
| func summarize(res []result) { | ||||
| 	sort.Slice(res, func(i, j int) bool { | ||||
| 		return res[i].domain <= res[j].domain | ||||
| 	}) | ||||
|  | ||||
| 	fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES") | ||||
| 	fmt.Println(strings.Repeat("-", dashLength)) | ||||
|  | ||||
| 	var total result | ||||
|  | ||||
| 	for _, r := range res { | ||||
| 		total = addResult(total, r) | ||||
| 		fmt.Printf(line, r.domain, r.page, r.visits, r.uniques) | ||||
| 	} | ||||
| 	fmt.Printf(footer, "TOTAL", total.visits, total.uniques) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user