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