move: interfaces error handling and advanced funcs
| @@ -1,73 +0,0 @@ | ||||
| // 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 | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| // 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) | ||||
| } | ||||
| @@ -1,82 +0,0 @@ | ||||
| // 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) | ||||
| } | ||||
| @@ -1,76 +0,0 @@ | ||||
| // 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 | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| // 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 } | ||||
| @@ -1,97 +0,0 @@ | ||||
| // 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 } | ||||
| @@ -1,29 +0,0 @@ | ||||
| SHELL := /bin/bash | ||||
| LINES = $$(wc -l log.txt | cut -f1 -d' ') | ||||
| ECHO_LINES = echo -e ">> log.txt has $(LINES) lines" | ||||
|  | ||||
| s:  | ||||
| 	time go run . < log.txt | ||||
|  | ||||
| r:  | ||||
| 	go run . < log.txt | ||||
|  | ||||
| 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) | ||||
|  | ||||
| lines: | ||||
| 	@$(ECHO_LINES) | ||||
| @@ -1,20 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"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 n < 0 || err != nil { | ||||
| 		f.err = fmt.Errorf("incorrect field -> %q = %q", name, val) | ||||
| 	} | ||||
| 	return n | ||||
| } | ||||
| @@ -1,34 +0,0 @@ | ||||
| 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") | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| 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 "" | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,16 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,16 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,16 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,59 +0,0 @@ | ||||
| // 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() { | ||||
| 	defer recoverErr() | ||||
|  | ||||
| 	_, err := newPipeline(). | ||||
| 		// from(fastTextReader(os.Stdin)). | ||||
| 		filterBy(notUsing(domainExtFilter("com", "io"))). | ||||
| 		groupBy(domainGrouper). | ||||
| 		start() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		fmt.Println("> Err:", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func recoverErr() { | ||||
| 	val := recover() | ||||
|  | ||||
| 	if val == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("> Error occurred:", val) | ||||
| } | ||||
|  | ||||
| /* | ||||
| newReport -> report.New(). | ||||
| Result    -> report.Line | ||||
|  | ||||
| notUsing = report.Not | ||||
|  | ||||
| pl := newPipeline(pipeOpts{ | ||||
|   from:     fastTextReader(os.Stdin), | ||||
|   filterBy: notUsing(domainExtFilter("com", "io")), | ||||
|   groupBy:  domainGrouper, | ||||
| }) | ||||
|  | ||||
| err := pl.start() | ||||
|  | ||||
| _, err := report.New(). | ||||
| 	From(report.TextReader(os.Stdin)). | ||||
| 	To(report.TextWriter(os.Stdout)). | ||||
| 	// FilterBy(report.OrgDomainsFilter). | ||||
| 	FilterBy(notUsing(report.DomainExtFilter("com", "io"))). | ||||
| 	GroupBy(report.DomainGrouper). | ||||
| 	Start() | ||||
| */ | ||||
| @@ -1,70 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	inputFn  func() ([]result, error) | ||||
| 	outputFn func([]result) error | ||||
| 	filterFn func(result) (include bool) | ||||
| 	groupFn  func(result) (key string) | ||||
| ) | ||||
|  | ||||
| type pipeline struct { | ||||
| 	input    inputFn | ||||
| 	filter   filterFn | ||||
| 	groupKey groupFn | ||||
| 	output   outputFn | ||||
| } | ||||
|  | ||||
| func newPipeline() *pipeline { | ||||
| 	return &pipeline{ | ||||
| 		filter:   noopFilter, | ||||
| 		groupKey: noopGrouper, | ||||
| 		input:    textReader(os.Stdin), | ||||
| 		output:   textWriter(os.Stdout), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *pipeline) from(fn inputFn) *pipeline      { p.input = fn; return p } | ||||
| func (p *pipeline) to(fn outputFn) *pipeline       { p.output = fn; return p } | ||||
| func (p *pipeline) filterBy(fn filterFn) *pipeline { p.filter = fn; return p } | ||||
| func (p *pipeline) groupBy(fn groupFn) *pipeline   { p.groupKey = fn; return p } | ||||
|  | ||||
| func (p *pipeline) start() ([]result, error) { | ||||
| 	res, err := p.input() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	gres := make(map[string]result) | ||||
|  | ||||
| 	for _, r := range res { | ||||
| 		if !p.filter(r) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		k := p.groupKey(r) | ||||
| 		gres[k] = r.add(gres[k]) | ||||
| 	} | ||||
|  | ||||
| 	var out []result | ||||
| 	for _, v := range gres { | ||||
| 		out = append(out, v) | ||||
| 	} | ||||
|  | ||||
| 	err = p.output(out) | ||||
|  | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| // TODO: remove me | ||||
| func measure(name string) func() { | ||||
| 	start := time.Now() | ||||
| 	return func() { | ||||
| 		fmt.Printf("%s took %v\n", name, time.Since(start)) | ||||
| 	} | ||||
| } | ||||
| @@ -1,36 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // result stores the parsed result for a domain | ||||
| type result struct { | ||||
| 	domain  string | ||||
| 	page    string | ||||
| 	visits  int | ||||
| 	uniques int | ||||
| } | ||||
|  | ||||
| // parseFields parses and returns the parsing result | ||||
| func parseFields(fields []string) (r result, err error) { | ||||
| 	if len(fields) != 4 { | ||||
| 		return r, fmt.Errorf("wrong number of fields -> %v", 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 | ||||
| } | ||||
|  | ||||
| // add adds the metrics of another result to the result | ||||
| func (r result) add(other result) result { | ||||
| 	r.visits += other.visits | ||||
| 	r.uniques += other.uniques | ||||
| 	return r | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| // 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" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func textReader(r io.Reader) inputFn { | ||||
| 	return func() ([]result, error) { | ||||
| 		return parseText(bufio.NewScanner(r)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TODO: custom error type for line information | ||||
| func parseText(in *bufio.Scanner) ([]result, error) { | ||||
| 	var res []result | ||||
|  | ||||
| 	for l := 1; in.Scan(); l++ { | ||||
| 		fields := strings.Fields(in.Text()) | ||||
| 		r, err := parseFields(fields) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
| 		res = append(res, r) | ||||
| 	} | ||||
|  | ||||
| 	return res, in.Err() | ||||
| } | ||||
| @@ -1,129 +0,0 @@ | ||||
| // 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" | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| // this could be made faster. | ||||
| // currently, it's 30-35% faster. | ||||
| // | ||||
| // so, what's different than the textreader? | ||||
| // | ||||
| // + creates the buffers specific to the input file/stdin size | ||||
| // + manually parses the fields: instead of strings.Fields | ||||
| // + gets the lines using scanner's Bytes() method: instead of Text() | ||||
| // + uses a manual atoi | ||||
| // + | ||||
|  | ||||
| func fastTextReader(r io.Reader) inputFn { | ||||
| 	return func() ([]result, error) { | ||||
| 		// first: count the lines, so the parseText can create | ||||
| 		// enough buffer. | ||||
| 		var buf bytes.Buffer | ||||
| 		l, err := countLines(io.TeeReader(r, &buf)) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		return fastParseText(bufio.NewScanner(&buf), l) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func fastParseText(in *bufio.Scanner, nlines int) ([]result, error) { | ||||
| 	// needs to know the number of total lines in the file | ||||
| 	res := make([]result, 0, nlines) | ||||
|  | ||||
| 	for l := 0; in.Scan(); l++ { | ||||
| 		_ = in.Bytes() | ||||
| 		r, err := fastParseFields(in.Bytes()) | ||||
| 		// r, err := result{"foo.com", "/bar", 10, 10}, error(nil) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("line %d: %v", l, err) | ||||
| 		} | ||||
| 		res = append(res, r) | ||||
| 	} | ||||
|  | ||||
| 	return res, in.Err() | ||||
| } | ||||
|  | ||||
| func fastParseFields(data []byte) (res result, err error) { | ||||
| 	var field int | ||||
|  | ||||
| 	for i, last := 0, 0; i < len(data); i++ { | ||||
| 		done := len(data) == i+1 | ||||
|  | ||||
| 		if c := data[i]; c == ' ' || done { | ||||
| 			if done { | ||||
| 				i = len(data) | ||||
| 			} | ||||
|  | ||||
| 			switch field { | ||||
| 			case 0: | ||||
| 				res.domain = string(data[last:i]) | ||||
| 			case 1: | ||||
| 				res.page = string(data[last:i]) | ||||
| 			case 2: | ||||
| 				res.visits, err = atoi(data[last:i]) | ||||
| 			case 3: | ||||
| 				res.uniques, err = atoi(data[last:i]) | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				return res, err | ||||
| 			} | ||||
|  | ||||
| 			last = i + 1 | ||||
| 			field++ | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if field != 4 { | ||||
| 		return result{}, errors.New("wrong number of fields") | ||||
| 	} | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| 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 | ||||
| } | ||||
|  | ||||
| func countLines(r io.Reader) (int, error) { | ||||
| 	var ( | ||||
| 		lines int | ||||
| 		buf   = make([]byte, os.Getpagesize()) // read via 16 KB blocks | ||||
| 	) | ||||
|  | ||||
| 	for { | ||||
| 		n, err := r.Read(buf) | ||||
| 		lines += bytes.Count(buf[:n], []byte{'\n'}) | ||||
|  | ||||
| 		if err == io.EOF { | ||||
| 			return lines, nil | ||||
| 		} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return lines, err | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"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 | ||||
| 	dashLength = 58 | ||||
| ) | ||||
|  | ||||
| func textWriter(w io.Writer) outputFn { | ||||
| 	return func(results []result) error { | ||||
| 		fmt.Fprintf(w, header, "DOMAINS", "PAGES", "VISITS", "UNIQUES") | ||||
| 		fmt.Fprintln(w, strings.Repeat("-", dashLength)) | ||||
|  | ||||
| 		var total result | ||||
| 		for _, r := range results { | ||||
| 			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 | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| learngoprogramming.com 10 | ||||
| learngoprogramming.com 10 | ||||
| golang.org 4 | ||||
| golang.org 6 | ||||
| blog.golang.org 20 | ||||
| blog.golang.org 10 | ||||
| @@ -1,6 +0,0 @@ | ||||
| learngoprogramming.com 10 | ||||
| learngoprogramming.com 10 | ||||
| golang.org | ||||
| golang.org 6 | ||||
| blog.golang.org 20 | ||||
| blog.golang.org 10 | ||||
| @@ -1,6 +0,0 @@ | ||||
| learngoprogramming.com 10 | ||||
| learngoprogramming.com 10 | ||||
| golang.org -100 | ||||
| golang.org 6 | ||||
| blog.golang.org 20 | ||||
| blog.golang.org 10 | ||||
| @@ -1,6 +0,0 @@ | ||||
| learngoprogramming.com 10 | ||||
| learngoprogramming.com 10 | ||||
| golang.org FOUR | ||||
| golang.org 6 | ||||
| blog.golang.org 20 | ||||
| blog.golang.org 10 | ||||
| @@ -1,26 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	p := newParser() | ||||
|  | ||||
| 	in := bufio.NewScanner(os.Stdin) | ||||
| 	for in.Scan() { | ||||
| 		res := parse(p, in.Text()) | ||||
| 		updateSummary(p.summary, res) | ||||
| 	} | ||||
|  | ||||
| 	summarize(summarizeParse(p)) | ||||
| 	dumpErrs(errParse(p), in.Err()) | ||||
| } | ||||
| @@ -1,70 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // 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 newParser() *parser { | ||||
| 	return &parser{summary: newSummary()} | ||||
| } | ||||
|  | ||||
| // parse parses a log line and adds it to the summary. | ||||
| func parse(p *parser, line string) (r result) { | ||||
| 	// if there was an error do not continue | ||||
| 	if p.lerr != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// chain the parser's error to the result's | ||||
| 	r = parseLine(p, line) | ||||
| 	if p.lines++; p.lerr != nil { | ||||
| 		p.lerr = fmt.Errorf("line #%d: %s", p.lines, p.lerr) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // parse parses a single log line | ||||
| func parseLine(p *parser, line string) (r result) { | ||||
| 	fields := strings.Fields(line) | ||||
| 	if len(fields) != 2 { | ||||
| 		// p.lerr = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) | ||||
| 		p.lerr = fmt.Errorf("missing fields: %v", fields) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	r.domain = fields[0] | ||||
| 	r.visits, p.lerr = strconv.Atoi(fields[1]) | ||||
|  | ||||
| 	if r.visits < 0 || p.lerr != nil { | ||||
| 		p.lerr = fmt.Errorf("incorrect visits: %q", fields[1]) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // summarizeParse summarizes the parsing results. | ||||
| // Only use it after the parsing is done. | ||||
| func summarizeParse(p *parser) *summary { | ||||
| 	return p.summary | ||||
| } | ||||
|  | ||||
| // errParse returns the last error encountered | ||||
| func errParse(p *parser) error { | ||||
| 	return p.lerr | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| // 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 | ||||
|  | ||||
| // 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 | ||||
| 	visits int | ||||
| 	// add more metrics if needed | ||||
| } | ||||
|  | ||||
| // add adds the metrics of another result to itself and returns a new Result | ||||
| func addResult(r result, other result) result { | ||||
| 	return result{ | ||||
| 		domain: r.domain, | ||||
| 		visits: r.visits + other.visits, | ||||
| 	} | ||||
| } | ||||
| @@ -1,36 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // summarize prints the parsing results. | ||||
| func summarize(s *summary) { | ||||
| 	fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") | ||||
| 	fmt.Println(strings.Repeat("-", 45)) | ||||
|  | ||||
| 	for next, cur := iteratorSummary(s); next(); { | ||||
| 		r := cur() | ||||
| 		fmt.Printf("%-30s %10d\n", r.domain, r.visits) | ||||
| 	} | ||||
|  | ||||
| 	t := totalsSummary(s) | ||||
| 	fmt.Printf("\n"+"%-30s %10d\n", "TOTAL", t.visits) | ||||
| } | ||||
|  | ||||
| // this variadic func simplifies the multiple error handling | ||||
| func dumpErrs(errs ...error) { | ||||
| 	for _, err := range errs { | ||||
| 		if err != nil { | ||||
| 			fmt.Printf("> Err: %s\n", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,70 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // 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)} | ||||
| } | ||||
|  | ||||
| // updateSummary updates the report for the given parsing result | ||||
| func updateSummary(s *summary, 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 = addResult(s.total, r) | ||||
| 	s.sum[domain] = addResult(r, s.sum[domain]) | ||||
| } | ||||
|  | ||||
| // iteratorSummary returns `next()` to detect when the iteration ends, | ||||
| // and a `cur()` to return the current result. | ||||
| // iterator iterates sorted by domains. | ||||
| func iteratorSummary(s *summary) (next func() bool, cur func() result) { | ||||
| 	sort.Strings(s.domains) | ||||
|  | ||||
| 	// remember the last iterated result | ||||
| 	var last int | ||||
|  | ||||
| 	next = func() bool { | ||||
| 		// done := len(s.domains) > last | ||||
| 		// last++ | ||||
| 		// return done | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| // totalsSummary returns the total metrics | ||||
| func totalsSummary(s *summary) result { | ||||
| 	return s.total | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,24 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	p := newParser() | ||||
|  | ||||
| 	in := bufio.NewScanner(os.Stdin) | ||||
| 	for in.Scan() { | ||||
| 		p.parse(in.Text()) | ||||
| 	} | ||||
|  | ||||
| 	summarize(p.summarize(), p.err(), in.Err()) | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| // 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 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 newParser() *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 := parseLine(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 | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // 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 | ||||
| 	visits    int | ||||
| 	timeSpent int | ||||
| 	// 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 parseLine(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 | ||||
| } | ||||
| @@ -1,51 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // 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 *summary, errors ...error) { | ||||
| 	if errs(errors...) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	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.totals() | ||||
| 	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 | ||||
| } | ||||
| @@ -1,70 +0,0 @@ | ||||
| // 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" | ||||
| ) | ||||
|  | ||||
| // 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 { | ||||
| 		// done := len(s.domains) > last | ||||
| 		// last++ | ||||
| 		// return done | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| // totals returns the total metrics | ||||
| func (s *summary) totals() result { | ||||
| 	return s.total | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,26 +0,0 @@ | ||||
| // 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/29-interfaces/logparser-pkg/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()) | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| // 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 | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| // 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 | ||||
| } | ||||
| @@ -1,85 +0,0 @@ | ||||
| // 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 ( | ||||
| 	"encoding/json" | ||||
| 	"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 | ||||
| } | ||||
|  | ||||
| // 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}, | ||||
| 	}) | ||||
| } | ||||
| @@ -1,73 +0,0 @@ | ||||
| // 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/29-interfaces/logparser-pkg/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 | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,6 +0,0 @@ | ||||
| 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 | ||||
| @@ -1,26 +0,0 @@ | ||||
| // 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/29-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()) | ||||
| } | ||||
| @@ -1,59 +0,0 @@ | ||||
| // +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) | ||||
| 	} | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| // 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 | ||||
| } | ||||
| @@ -1,55 +0,0 @@ | ||||
| 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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| // 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 | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| // 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}, | ||||
| // 	}) | ||||
| // } | ||||
| @@ -1,44 +0,0 @@ | ||||
| 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) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,73 +0,0 @@ | ||||
| // 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/29-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 | ||||
| } | ||||
| Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB | 
| Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB | 
| Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB | 
| Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB | 
| Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB | 
| Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB | 
| Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB | 
| Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |