move: interfaces error handling and advanced funcs

This commit is contained in:
Inanc Gumus
2019-08-17 15:54:40 +03:00
parent 4dd469ff18
commit ccfa68200a
92 changed files with 0 additions and 2313 deletions

View File

@ -1,73 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
func main() {
nums := []int{2, 3, 7}
fmt.Printf("nums : %d\n", nums)
n := avgNoVariadic(nums)
fmt.Printf("avgNoVariadic : %d\n", n)
n = avg(2, 3, 7)
fmt.Printf("avg(2, 3, 7) : %d\n", n)
n = avg(2, 3, 7, 8)
fmt.Printf("avg(2, 3, 7, 8) : %d\n", n)
// use ... to pass a slice
n = avg(nums...)
fmt.Printf("avg(nums...) : %d\n", n)
// uses the existing slice
double(nums...)
fmt.Printf("double(nums...) : %d\n", nums)
// creates a new slice
double(4, 6, 14)
fmt.Printf("double(4, 6, 14): %d\n", nums)
// creates a nil slice
fmt.Printf("\nmain.nums : %p\n", nums)
investigate("passes main.nums", nums...)
investigate("passes main.nums", nums...)
investigate("passes args", 4, 6, 14)
investigate("passes args", 4, 6, 14)
investigate("no args")
}
func investigate(msg string, nums ...int) {
fmt.Printf("investigate.nums: %12p -> %s\n", nums, msg)
if len(nums) > 0 {
fmt.Printf("\tfirst element: %d\n", nums[0])
}
}
func double(nums ...int) {
for i := range nums {
nums[i] *= 2
}
}
func avg(nums ...int) int {
return sum(nums) / len(nums)
}
func avgNoVariadic(nums []int) int {
return sum(nums) / len(nums)
}
func sum(nums []int) (total int) {
for _, n := range nums {
total += n
}
return
}

View File

@ -1,52 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type filterFunc func(int) bool
func main() {
signatures()
funcValues()
}
func isEven(n int) bool {
return n%2 == 0
}
func isOdd(m int) bool {
return m%2 == 1
}
func signatures() {
fmt.Println("••• FUNC SIGNATURES (TYPES) •••")
fmt.Printf("isEven : %T\n", isEven)
fmt.Printf("isOdd : %T\n", isOdd)
}
func funcValues() {
fmt.Println("\n••• FUNC VALUES (VARS) •••")
// var value func(int) bool
var value filterFunc
fmt.Printf("value nil? : %t\n", value == nil)
value = isEven
fmt.Printf("value(2) : %t\n", value(2))
fmt.Printf("isEven(2) : %t\n", isEven(2))
value = isOdd
fmt.Printf("value(1) : %t\n", value(1))
fmt.Printf("isOdd(1) : %t\n", isOdd(1))
fmt.Printf("value nil? : %t\n", value == nil)
fmt.Printf("value : %p\n", value)
fmt.Printf("isEven : %p\n", isEven)
fmt.Printf("isOdd : %p\n", isOdd)
}

View File

@ -1,82 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strings"
"unicode"
)
type filterFunc func(int) bool
func main() {
signatures()
fmt.Println("\n••• WITHOUT FUNC VALUES •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("evens : %d\n", filterEvens(nums...))
fmt.Printf("odds : %d\n", filterOdds(nums...))
fmt.Println("\n••• FUNC VALUES •••")
fmt.Printf("evens : %d\n", filter(isEven, nums...))
fmt.Printf("odds : %d\n", filter(isOdd, nums...))
fmt.Println("\n••• MAPPING •••")
fmt.Println(strings.Map(unpunct, "hello!!! HOW ARE YOU???? :))"))
fmt.Println(strings.Map(unpunct, "TIME IS UP!"))
}
func unpunct(r rune) rune {
if unicode.IsPunct(r) {
return -1
}
return unicode.ToLower(r)
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func filterOdds(nums ...int) (filtered []int) {
for _, n := range nums {
if isOdd(n) {
filtered = append(filtered, n)
}
}
return
}
func filterEvens(nums ...int) (filtered []int) {
for _, n := range nums {
if isEven(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool {
return n%2 == 0
}
func isOdd(m int) bool {
return m%2 == 1
}
func signatures() {
fmt.Println("••• FUNC SIGNATURES (TYPES) •••")
fmt.Printf("isEven : %T\n", isEven)
fmt.Printf("isOdd : %T\n", isOdd)
}

View File

@ -1,76 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type filterFunc func(int) bool
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("••• FUNC VALUES •••")
fmt.Printf("evens : %d\n", filter(isEven, nums...))
fmt.Printf("odds : %d\n", filter(isOdd, nums...))
fmt.Printf("> 3 : %d\n", filter(greaterThan3, nums...))
fmt.Printf("> 6 : %d\n", filter(greaterThan6, nums...))
// ========================================================================
fmt.Println("\n••• CLOSURES •••")
var min int
greaterThan := func(n int) bool {
return n > min
}
min = 3
fmt.Printf("> 3 : %d\n", filter(greaterThan, nums...))
min = 6
fmt.Printf("> 6 : %d\n", filter(greaterThan, nums...))
// min = 1
// fmt.Printf("> 1 : %d\n", filter(greaterThan, nums...))
// min = 2
// fmt.Printf("> 2 : %d\n", filter(greaterThan, nums...))
// min = 3
// fmt.Printf("> 3 : %d\n", filter(greaterThan, nums...))
var filterers []filterFunc
for i := 1; i <= 3; i++ {
current := i
filterers = append(filterers, func(n int) bool {
min = current
return greaterThan(n)
})
}
printer(filterers, nums...)
}
func printer(filterers []filterFunc, nums ...int) {
for i, filterer := range filterers {
fmt.Printf("> %d : %d\n", i+1, filter(filterer, nums...))
}
}
func greaterThan6(n int) bool { return n > 6 }
func greaterThan3(n int) bool { return n > 3 }
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}

View File

@ -1,58 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
type filterFunc func(int) bool
func main() {
fmt.Println("••• HIGHER-ORDER FUNCS •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := reverse(reverse(isEven))
fmt.Printf("reversed : %t\n", odd(8))
fmt.Printf("> 3 : %d\n", filter(greater(3), nums...))
fmt.Printf("> 6 : %d\n", filter(greater(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(lesseq(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(reverse(greater(6)), nums...))
}
func reverse(f filterFunc) filterFunc {
return func(n int) bool {
return !f(n)
}
}
func greater(min int) filterFunc {
return func(n int) bool {
return n > min
}
}
func lesseq(max int) filterFunc {
return func(n int) bool {
return n <= max
}
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }

View File

@ -1,97 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
type filterFunc func(int) bool
func main() {
fmt.Println("••• HIGHER-ORDER FUNCS •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := reverse(reverse(isEven))
fmt.Printf("reversed : %t\n", odd(8))
fmt.Printf("> 3 : %d\n", filter(greater(3), nums...))
fmt.Printf("> 6 : %d\n", filter(greater(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(lesseq(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(reverse(greater(6)), nums...))
fmt.Println("\n••• CLOSURES •••")
fmt.Printf("uniques : %d\n", filter(uniques(), 1, 1, 2, 3, 3))
dups := reverse(uniques())
fmt.Printf("dups : %v\n", filter(dups, 1, 1, 2, 3, 3))
dups = reverse(uniques())
fmt.Printf("dups dups : %v\n", filter(dups, 1, 1, 2, 3, 3, 3, 3))
dups = reverse(uniques())
out := filter(dups, 1, 1, 2, 3, 3, 3, 3)
fmt.Printf("dups uniqs : %v\n", filter(uniques(), out...))
dups = reverse(uniques())
chained := chain(reverse(greater(1)), dups, uniques())
fmt.Printf("dups chain : %v\n", filter(chained, 1, 1, 2, 3, 3, 3, 3, 0, 0))
}
func chain(filters ...filterFunc) filterFunc {
return func(n int) bool {
for _, next := range filters {
if !next(n) {
return false
}
}
return true
}
}
func uniques() filterFunc {
seen := make(map[int]bool)
return func(n int) (ok bool) {
if !seen[n] {
seen[n], ok = true, true
}
return
}
}
func reverse(f filterFunc) filterFunc {
return func(n int) bool {
return !f(n)
}
}
func greater(min int) filterFunc {
return func(n int) bool {
return n > min
}
}
func lesseq(max int) filterFunc {
return func(n int) bool {
return n <= max
}
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }

View File

@ -1,29 +0,0 @@
SHELL := /bin/bash
LINES = $$(wc -l log.txt | cut -f1 -d' ')
ECHO_LINES = echo -e ">> log.txt has $(LINES) lines"
s:
time go run . < log.txt
r:
go run . < log.txt
n ?= 18
load: restore
@echo "enlarging the file with itself, please wait..."
@for i in {1..$(n)}; do \
awk 1 log.txt log.txt > log_.txt; \
mv log_.txt log.txt; \
rm -f log_.txt; \
done
@$(ECHO_LINES)
restore:
@echo "restoring the file..."
@git checkout log.txt
@$(ECHO_LINES)
lines:
@$(ECHO_LINES)

View File

@ -1,20 +0,0 @@
package main
import (
"fmt"
"strconv"
)
// field helps for field parsing
type field struct{ err error }
// uatoi parses an unsigned integer string and saves the error.
// it assumes that the val is unsigned.
// for ease of usability: it returns an int instead of uint.
func (f *field) uatoi(name, val string) int {
n, err := strconv.Atoi(val)
if n < 0 || err != nil {
f.err = fmt.Errorf("incorrect field -> %q = %q", name, val)
}
return n
}

View File

@ -1,34 +0,0 @@
package main
import "strings"
func noopFilter(r result) bool {
return true
}
func notUsing(filter filterFn) filterFn {
return func(r result) bool {
return !filter(r)
}
}
func domainExtFilter(domains ...string) filterFn {
return func(r result) bool {
for _, domain := range domains {
if strings.HasSuffix(r.domain, "."+domain) {
return true
}
}
return false
}
}
func domainFilter(domain string) filterFn {
return func(r result) bool {
return strings.Contains(r.domain, domain)
}
}
func orgDomainsFilter(r result) bool {
return strings.HasSuffix(r.domain, ".org")
}

View File

@ -1,20 +0,0 @@
package main
// domainGrouper groups by domain.
// but it keeps the other fields.
// for example: it returns pages as well, but you shouldn't use them.
// exercise: write a function that erases superfluous data.
func domainGrouper(r result) string {
return r.domain
}
func pageGrouper(r result) string {
return r.domain + r.page
}
// groupBy allocates map unnecessarily
func noopGrouper(r result) string {
// with something like:
// return randomStrings()
return ""
}

View File

@ -1,16 +0,0 @@
learngoprogramming.com / 10 5
learngoprogramming.com /courses 15 10
learngoprogramming.com /courses 10 5
learngoprogramming.com /articles 20 15
learngoprogramming.com /articles 5 2
golang.org / 40 20
golang.org / 20 10
golang.org /blog 45 25
golang.org /blog 15 5
blog.golang.org /courses 60 30
blog.golang.org /courses 30 20
blog.golang.org /updates 20 10
blog.golang.org /reference 65 35
blog.golang.org /reference 15 5
inanc.io /about 30 15
inanc.io /about 70 35

View File

@ -1,16 +0,0 @@
learngoprogramming.com / 10 5
learngoprogramming.com /courses 15 10
learngoprogramming.com /courses 10 5
learngoprogramming.com /articles 20 15
learngoprogramming.com /articles 5 2
golang.org / 40 20
golang.org / 20 10
golang.org /blog 45 25
golang.org /blog 15 5
blog.golang.org /updates
blog.golang.org /updates 30 20
blog.golang.org /updates 20 10
blog.golang.org /reference 65 35
blog.golang.org /reference 15 5
inanc.io /about 30 15
inanc.io /about 70 35

View File

@ -1,16 +0,0 @@
learngoprogramming.com / 10 5
learngoprogramming.com /courses 15 10
learngoprogramming.com /courses 10 5
learngoprogramming.com /articles 20 15
learngoprogramming.com /articles 5 2
golang.org / 40 20
golang.org / 20 10
golang.org /blog 45 -250
golang.org /blog 15 5
blog.golang.org /updates 60 30
blog.golang.org /updates 30 20
blog.golang.org /updates 20 10
blog.golang.org /reference 65 35
blog.golang.org /reference 15 5
inanc.io /about 30 15
inanc.io /about 70 35

View File

@ -1,16 +0,0 @@
learngoprogramming.com / 10 5
learngoprogramming.com /courses 15 10
learngoprogramming.com /courses 10 5
learngoprogramming.com /articles 20 15
learngoprogramming.com /articles 5 2
golang.org / 40 TWENTY
golang.org / 20 10
golang.org /blog 45 25
golang.org /blog 15 5
blog.golang.org /updates 60 30
blog.golang.org /updates 30 20
blog.golang.org /updates 20 10
blog.golang.org /reference 65 35
blog.golang.org /reference 15 5
inanc.io /about 30 15
inanc.io /about 70 35

View File

@ -1,59 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
func main() {
defer recoverErr()
_, err := newPipeline().
// from(fastTextReader(os.Stdin)).
filterBy(notUsing(domainExtFilter("com", "io"))).
groupBy(domainGrouper).
start()
if err != nil {
fmt.Println("> Err:", err)
}
}
func recoverErr() {
val := recover()
if val == nil {
return
}
fmt.Println("> Error occurred:", val)
}
/*
newReport -> report.New().
Result -> report.Line
notUsing = report.Not
pl := newPipeline(pipeOpts{
from: fastTextReader(os.Stdin),
filterBy: notUsing(domainExtFilter("com", "io")),
groupBy: domainGrouper,
})
err := pl.start()
_, err := report.New().
From(report.TextReader(os.Stdin)).
To(report.TextWriter(os.Stdout)).
// FilterBy(report.OrgDomainsFilter).
FilterBy(notUsing(report.DomainExtFilter("com", "io"))).
GroupBy(report.DomainGrouper).
Start()
*/

View File

@ -1,70 +0,0 @@
package main
import (
"fmt"
"os"
"time"
)
type (
inputFn func() ([]result, error)
outputFn func([]result) error
filterFn func(result) (include bool)
groupFn func(result) (key string)
)
type pipeline struct {
input inputFn
filter filterFn
groupKey groupFn
output outputFn
}
func newPipeline() *pipeline {
return &pipeline{
filter: noopFilter,
groupKey: noopGrouper,
input: textReader(os.Stdin),
output: textWriter(os.Stdout),
}
}
func (p *pipeline) from(fn inputFn) *pipeline { p.input = fn; return p }
func (p *pipeline) to(fn outputFn) *pipeline { p.output = fn; return p }
func (p *pipeline) filterBy(fn filterFn) *pipeline { p.filter = fn; return p }
func (p *pipeline) groupBy(fn groupFn) *pipeline { p.groupKey = fn; return p }
func (p *pipeline) start() ([]result, error) {
res, err := p.input()
if err != nil {
return nil, err
}
gres := make(map[string]result)
for _, r := range res {
if !p.filter(r) {
continue
}
k := p.groupKey(r)
gres[k] = r.add(gres[k])
}
var out []result
for _, v := range gres {
out = append(out, v)
}
err = p.output(out)
return out, err
}
// TODO: remove me
func measure(name string) func() {
start := time.Now()
return func() {
fmt.Printf("%s took %v\n", name, time.Since(start))
}
}

View File

@ -1,36 +0,0 @@
package main
import (
"fmt"
)
// result stores the parsed result for a domain
type result struct {
domain string
page string
visits int
uniques int
}
// parseFields parses and returns the parsing result
func parseFields(fields []string) (r result, err error) {
if len(fields) != 4 {
return r, fmt.Errorf("wrong number of fields -> %v", fields)
}
r.domain = fields[0]
r.page = fields[1]
f := new(field)
r.visits = f.uatoi("visits", fields[2])
r.uniques = f.uatoi("uniques", fields[3])
return r, f.err
}
// add adds the metrics of another result to the result
func (r result) add(other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}

View File

@ -1,38 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
func textReader(r io.Reader) inputFn {
return func() ([]result, error) {
return parseText(bufio.NewScanner(r))
}
}
// TODO: custom error type for line information
func parseText(in *bufio.Scanner) ([]result, error) {
var res []result
for l := 1; in.Scan(); l++ {
fields := strings.Fields(in.Text())
r, err := parseFields(fields)
if err != nil {
return nil, fmt.Errorf("line %d: %v", l, err)
}
res = append(res, r)
}
return res, in.Err()
}

View File

@ -1,129 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
)
// this could be made faster.
// currently, it's 30-35% faster.
//
// so, what's different than the textreader?
//
// + creates the buffers specific to the input file/stdin size
// + manually parses the fields: instead of strings.Fields
// + gets the lines using scanner's Bytes() method: instead of Text()
// + uses a manual atoi
// +
func fastTextReader(r io.Reader) inputFn {
return func() ([]result, error) {
// first: count the lines, so the parseText can create
// enough buffer.
var buf bytes.Buffer
l, err := countLines(io.TeeReader(r, &buf))
if err != nil {
return nil, err
}
return fastParseText(bufio.NewScanner(&buf), l)
}
}
func fastParseText(in *bufio.Scanner, nlines int) ([]result, error) {
// needs to know the number of total lines in the file
res := make([]result, 0, nlines)
for l := 0; in.Scan(); l++ {
_ = in.Bytes()
r, err := fastParseFields(in.Bytes())
// r, err := result{"foo.com", "/bar", 10, 10}, error(nil)
if err != nil {
return nil, fmt.Errorf("line %d: %v", l, err)
}
res = append(res, r)
}
return res, in.Err()
}
func fastParseFields(data []byte) (res result, err error) {
var field int
for i, last := 0, 0; i < len(data); i++ {
done := len(data) == i+1
if c := data[i]; c == ' ' || done {
if done {
i = len(data)
}
switch field {
case 0:
res.domain = string(data[last:i])
case 1:
res.page = string(data[last:i])
case 2:
res.visits, err = atoi(data[last:i])
case 3:
res.uniques, err = atoi(data[last:i])
}
if err != nil {
return res, err
}
last = i + 1
field++
}
}
if field != 4 {
return result{}, errors.New("wrong number of fields")
}
return res, nil
}
func atoi(input []byte) (int, error) {
val := 0
for i := 0; i < len(input); i++ {
char := input[i]
if char < '0' || char > '9' {
return 0, errors.New("invalid number")
}
val = val*10 + int(char) - '0'
}
return val, nil
}
func countLines(r io.Reader) (int, error) {
var (
lines int
buf = make([]byte, os.Getpagesize()) // read via 16 KB blocks
)
for {
n, err := r.Read(buf)
lines += bytes.Count(buf[:n], []byte{'\n'})
if err == io.EOF {
return lines, nil
}
if err != nil {
return lines, err
}
}
}

View File

@ -1,43 +0,0 @@
package main
import (
"fmt"
"io"
"strings"
)
// TODO: sort by result key interfaces section
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dashLength = 58
)
func textWriter(w io.Writer) outputFn {
return func(results []result) error {
fmt.Fprintf(w, header, "DOMAINS", "PAGES", "VISITS", "UNIQUES")
fmt.Fprintln(w, strings.Repeat("-", dashLength))
var total result
for _, r := range results {
total = total.add(r)
fmt.Fprintf(w, line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Fprintf(w, footer, "", total.visits, total.uniques)
return nil
}
}
func noWhere() outputFn {
return func(res []result) error {
return nil
}
}

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org 4
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org -100
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org FOUR
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,26 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"os"
)
func main() {
p := newParser()
in := bufio.NewScanner(os.Stdin)
for in.Scan() {
res := parse(p, in.Text())
updateSummary(p.summary, res)
}
summarize(summarizeParse(p))
dumpErrs(errParse(p), in.Err())
}

View File

@ -1,70 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
// parser parses the log file and generates a summary report.
type parser struct {
summary *summary // summarizes the parsing results
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// new returns a new parsing state.
func newParser() *parser {
return &parser{summary: newSummary()}
}
// parse parses a log line and adds it to the summary.
func parse(p *parser, line string) (r result) {
// if there was an error do not continue
if p.lerr != nil {
return
}
// chain the parser's error to the result's
r = parseLine(p, line)
if p.lines++; p.lerr != nil {
p.lerr = fmt.Errorf("line #%d: %s", p.lines, p.lerr)
}
return
}
// parse parses a single log line
func parseLine(p *parser, line string) (r result) {
fields := strings.Fields(line)
if len(fields) != 2 {
// p.lerr = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines)
p.lerr = fmt.Errorf("missing fields: %v", fields)
return
}
r.domain = fields[0]
r.visits, p.lerr = strconv.Atoi(fields[1])
if r.visits < 0 || p.lerr != nil {
p.lerr = fmt.Errorf("incorrect visits: %q", fields[1])
}
return
}
// summarizeParse summarizes the parsing results.
// Only use it after the parsing is done.
func summarizeParse(p *parser) *summary {
return p.summary
}
// errParse returns the last error encountered
func errParse(p *parser) error {
return p.lerr
}

View File

@ -1,27 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
// always put all the related things together as in here
// result stores metrics for a domain
// it uses the value mechanics,
// because it doesn't have to update anything
type result struct {
domain string
visits int
// add more metrics if needed
}
// add adds the metrics of another result to itself and returns a new Result
func addResult(r result, other result) result {
return result{
domain: r.domain,
visits: r.visits + other.visits,
}
}

View File

@ -1,36 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strings"
)
// summarize prints the parsing results.
func summarize(s *summary) {
fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS")
fmt.Println(strings.Repeat("-", 45))
for next, cur := iteratorSummary(s); next(); {
r := cur()
fmt.Printf("%-30s %10d\n", r.domain, r.visits)
}
t := totalsSummary(s)
fmt.Printf("\n"+"%-30s %10d\n", "TOTAL", t.visits)
}
// this variadic func simplifies the multiple error handling
func dumpErrs(errs ...error) {
for _, err := range errs {
if err != nil {
fmt.Printf("> Err: %s\n", err)
}
}
}

View File

@ -1,70 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"sort"
)
// summary aggregates the parsing results
type summary struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
total result // total visits for all domains
}
// newSummary constructs and initializes a new summary
// You can't use its methods without pointer mechanics
func newSummary() *summary {
return &summary{sum: make(map[string]result)}
}
// updateSummary updates the report for the given parsing result
func updateSummary(s *summary, r result) {
domain := r.domain
if _, ok := s.sum[domain]; !ok {
s.domains = append(s.domains, domain)
}
// let the result handle the addition
// this allows us to manage the result in once place
// and this way it becomes easily extendable
s.total = addResult(s.total, r)
s.sum[domain] = addResult(r, s.sum[domain])
}
// iteratorSummary returns `next()` to detect when the iteration ends,
// and a `cur()` to return the current result.
// iterator iterates sorted by domains.
func iteratorSummary(s *summary) (next func() bool, cur func() result) {
sort.Strings(s.domains)
// remember the last iterated result
var last int
next = func() bool {
// done := len(s.domains) > last
// last++
// return done
defer func() { last++ }()
return len(s.domains) > last
}
cur = func() result {
// returns a copy so the caller cannot change it
name := s.domains[last-1]
return s.sum[name]
}
return
}
// totalsSummary returns the total metrics
func totalsSummary(s *summary) result {
return s.total
}

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 300
golang.org 4 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10
golang.org 4 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 300
golang.org -100 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 THREE-HUNDRED
golang.org FOUR 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,24 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"os"
)
func main() {
p := newParser()
in := bufio.NewScanner(os.Stdin)
for in.Scan() {
p.parse(in.Text())
}
summarize(p.summarize(), p.err(), in.Err())
}

View File

@ -1,52 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
// parser parses the log file and generates a summary report.
type parser struct {
summary *summary // summarizes the parsing results
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// new returns a new parsing state.
func newParser() *parser {
return &parser{summary: newSummary()}
}
// parse parses a log line and adds it to the summary.
func (p *parser) parse(line string) {
// if there was an error do not continue
if p.lerr != nil {
return
}
// chain the parser's error to the result's
res, err := parseLine(line)
if p.lines++; err != nil {
p.lerr = fmt.Errorf("line #%d: %s", p.lines, err)
return
}
p.summary.update(res)
}
// Summarize summarizes the parsing results.
// Only use it after the parsing is done.
func (p *parser) summarize() *summary {
return p.summary
}
// Err returns the last error encountered
func (p *parser) err() error {
return p.lerr
}

View File

@ -1,60 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
// always put all the related things together as in here
// result stores metrics for a domain
// it uses the value mechanics,
// because it doesn't have to update anything
type result struct {
domain string
visits int
timeSpent int
// add more metrics if needed
}
// add adds the metrics of another result to itself and returns a new Result
func (r result) add(other result) result {
return result{
domain: r.domain,
visits: r.visits + other.visits,
timeSpent: r.timeSpent + other.timeSpent,
}
}
// parse parses a single log line
func parseLine(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != 3 {
return r, fmt.Errorf("missing fields: %v", fields)
}
f := new(field)
r.domain = fields[0]
r.visits = f.atoi("visits", fields[1])
r.timeSpent = f.atoi("time spent", fields[2])
return r, f.err
}
// field helps for field parsing
type field struct{ err error }
func (f *field) atoi(name, val string) int {
n, err := strconv.Atoi(val)
if n < 0 || err != nil {
f.err = fmt.Errorf("incorrect %s: %q", name, val)
}
return n
}

View File

@ -1,51 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strings"
)
// summarize prints the parsing results.
//
// it prints the errors and returns if there are any.
//
// --json flag encodes to json and prints.
func summarize(sum *summary, errors ...error) {
if errs(errors...) {
return
}
const (
head = "%-30s %10s %20s\n"
val = "%-30s %10d %20d\n"
)
fmt.Printf(head, "DOMAIN", "VISITS", "TIME SPENT")
fmt.Println(strings.Repeat("-", 65))
for next, cur := sum.iterator(); next(); {
r := cur()
fmt.Printf(val, r.domain, r.visits, r.timeSpent)
}
t := sum.totals()
fmt.Printf("\n"+val, "TOTAL", t.visits, t.timeSpent)
}
// this variadic func simplifies the multiple error handling
func errs(errs ...error) (wasErr bool) {
for _, err := range errs {
if err != nil {
fmt.Printf("> Err: %s\n", err)
wasErr = true
}
}
return
}

View File

@ -1,70 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"sort"
)
// summary aggregates the parsing results
type summary struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
total result // total visits for all domains
}
// newSummary constructs and initializes a new summary
// You can't use its methods without pointer mechanics
func newSummary() *summary {
return &summary{sum: make(map[string]result)}
}
// Update updates the report for the given parsing result
func (s *summary) update(r result) {
domain := r.domain
if _, ok := s.sum[domain]; !ok {
s.domains = append(s.domains, domain)
}
// let the result handle the addition
// this allows us to manage the result in once place
// and this way it becomes easily extendable
s.total = s.total.add(r)
s.sum[domain] = r.add(s.sum[domain])
}
// Iterator returns `next()` to detect when the iteration ends,
// and a `cur()` to return the current result.
// iterator iterates sorted by domains.
func (s *summary) iterator() (next func() bool, cur func() result) {
sort.Strings(s.domains)
// remember the last iterated result
var last int
next = func() bool {
// done := len(s.domains) > last
// last++
// return done
defer func() { last++ }()
return len(s.domains) > last
}
cur = func() result {
// returns a copy so the caller cannot change it
name := s.domains[last-1]
return s.sum[name]
}
return
}
// totals returns the total metrics
func (s *summary) totals() result {
return s.total
}

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 300
golang.org 4 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10
golang.org 4 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 300
golang.org -100 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 THREE-HUNDRED
golang.org FOUR 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,26 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"os"
"github.com/inancgumus/learngo/29-interfaces/logparser-pkg/report"
)
func main() {
p := report.New()
in := bufio.NewScanner(os.Stdin)
for in.Scan() {
p.Parse(in.Text())
}
summarize(p.Summarize(), p.Err(), in.Err())
}

View File

@ -1,52 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package report
import (
"fmt"
)
// Parser parses the log file and generates a summary report.
type Parser struct {
summary *Summary // summarizes the parsing results
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// New returns a new parsing state.
func New() *Parser {
return &Parser{summary: newSummary()}
}
// Parse parses a log line and adds it to the summary.
func (p *Parser) Parse(line string) {
// if there was an error do not continue
if p.lerr != nil {
return
}
// chain the parser's error to the result's
res, err := parse(line)
if p.lines++; err != nil {
p.lerr = fmt.Errorf("line #%d: %s", p.lines, err)
return
}
p.summary.update(res)
}
// Summarize summarizes the parsing results.
// Only use it after the parsing is done.
func (p *Parser) Summarize() *Summary {
return p.summary
}
// Err returns the last error encountered
func (p *Parser) Err() error {
return p.lerr
}

View File

@ -1,60 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package report
import (
"fmt"
"strconv"
"strings"
)
// always put all the related things together as in here
// Result stores metrics for a domain
// it uses the value mechanics,
// because it doesn't have to update anything
type Result struct {
Domain string `json:"domain"`
Visits int `json:"visits"`
TimeSpent int `json:"time_spent"`
// add more metrics if needed
}
// add adds the metrics of another Result to itself and returns a new Result
func (r Result) add(other Result) Result {
return Result{
Domain: r.Domain,
Visits: r.Visits + other.Visits,
TimeSpent: r.TimeSpent + other.TimeSpent,
}
}
// parse parses a single log line
func parse(line string) (r Result, err error) {
fields := strings.Fields(line)
if len(fields) != 3 {
return r, fmt.Errorf("missing fields: %v", fields)
}
f := new(field)
r.Domain = fields[0]
r.Visits = f.atoi("visits", fields[1])
r.TimeSpent = f.atoi("time spent", fields[2])
return r, f.err
}
// field helps for field parsing
type field struct{ err error }
func (f *field) atoi(name, val string) int {
n, err := strconv.Atoi(val)
if n < 0 || err != nil {
f.err = fmt.Errorf("incorrect %s: %q", name, val)
}
return n
}

View File

@ -1,85 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package report
import (
"encoding/json"
"sort"
)
// Summary aggregates the parsing results
type Summary struct {
sum map[string]Result // metrics per domain
domains []string // unique domain names
total Result // total visits for all domains
}
// newSummary constructs and initializes a new summary
// You can't use its methods without pointer mechanics
func newSummary() *Summary {
return &Summary{sum: make(map[string]Result)}
}
// Update updates the report for the given parsing result
func (s *Summary) update(r Result) {
domain := r.Domain
if _, ok := s.sum[domain]; !ok {
s.domains = append(s.domains, domain)
}
// let the result handle the addition
// this allows us to manage the result in once place
// and this way it becomes easily extendable
s.total = s.total.add(r)
s.sum[domain] = r.add(s.sum[domain])
}
// Iterator returns `next()` to detect when the iteration ends,
// and a `cur()` to return the current result.
// iterator iterates sorted by domains.
func (s *Summary) Iterator() (next func() bool, cur func() Result) {
sort.Strings(s.domains)
// remember the last iterated result
var last int
next = func() bool {
defer func() { last++ }()
return len(s.domains) > last
}
cur = func() Result {
// returns a copy so the caller cannot change it
name := s.domains[last-1]
return s.sum[name]
}
return
}
// Total returns the total metrics
func (s *Summary) Total() Result {
return s.total
}
// MarshalJSON marshals a report to JSON
// Alternative: unexported embedding
func (s *Summary) MarshalJSON() ([]byte, error) {
type total struct {
*Result
IgnoreDomain *string `json:"domain,omitempty"`
}
return json.Marshal(struct {
Sum map[string]Result `json:"summary"`
Domains []string `json:"domains"`
Total total `json:"total"`
}{
Sum: s.sum, Domains: s.domains, Total: total{Result: &s.total},
})
}

View File

@ -1,73 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/inancgumus/learngo/29-interfaces/logparser-pkg/report"
)
// summarize prints the parsing results.
//
// it prints the errors and returns if there are any.
//
// --json flag encodes to json and prints.
func summarize(sum *report.Summary, errors ...error) {
if errs(errors...) {
return
}
if args := os.Args[1:]; len(args) == 1 && args[0] == "--json" {
encode(sum)
return
}
stdout(sum)
}
// encodes the summary to json
func encode(sum *report.Summary) {
out, err := json.MarshalIndent(sum, "", "\t")
if err != nil {
panic(err)
}
os.Stdout.Write(out)
}
// prints the summary to standard out
func stdout(sum *report.Summary) {
const (
head = "%-30s %10s %20s\n"
val = "%-30s %10d %20d\n"
)
fmt.Printf(head, "DOMAIN", "VISITS", "TIME SPENT")
fmt.Println(strings.Repeat("-", 65))
for next, cur := sum.Iterator(); next(); {
r := cur()
fmt.Printf(val, r.Domain, r.Visits, r.TimeSpent)
}
t := sum.Total()
fmt.Printf("\n"+val, "TOTAL", t.Visits, t.TimeSpent)
}
// this variadic func simplifies the multiple error handling
func errs(errs ...error) (wasErr bool) {
for _, err := range errs {
if err != nil {
fmt.Printf("> Err: %s\n", err)
wasErr = true
}
}
return
}

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 300
golang.org 4 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10
golang.org 4 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 300
golang.org -100 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10 200
learngoprogramming.com 10 THREE-HUNDRED
golang.org FOUR 50
golang.org 6 100
blog.golang.org 20 25
blog.golang.org 10 1

View File

@ -1,26 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"os"
"github.com/inancgumus/learngo/29-interfaces/logparser-testing/report"
)
func main() {
p := report.New()
in := bufio.NewScanner(os.Stdin)
for in.Scan() {
p.Parse(in.Text())
}
summarize(p.Summarize(), p.Err(), in.Err())
}

View File

@ -1,59 +0,0 @@
// +build integration
// go test -tags=integration
package main_test
import (
"bytes"
"os/exec"
"strings"
"testing"
)
const (
okIn = `
a.com 1 2
b.com 3 4
a.com 4 5
b.com 6 7`
okOut = `
DOMAIN VISITS TIME SPENT
-----------------------------------------------------------------
a.com 5 7
b.com 9 11
TOTAL 14 18`
)
func TestSummary(t *testing.T) {
tests := []struct {
name, in, out string
}{
{"valid input", okIn, okOut},
{"missing fields", "a.com 1 2\nb.com 3", "> Err: line #2: missing fields: [b.com 3]"},
{"incorrect visits", "a.com 1 2\nb.com -1 1", `> Err: line #2: incorrect visits: "-1"`},
{"incorrect time spent", "a.com 1 2\nb.com 3 -1", `> Err: line #2: incorrect time spent: "-1"`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
run(t, strings.TrimSpace(tt.in), strings.TrimSpace(tt.out))
})
}
}
func run(t *testing.T, in, out string) {
cmd := exec.Command("go", "run", ".")
cmd.Stdin = strings.NewReader(in)
got, err := cmd.CombinedOutput()
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, []byte(out+"\n")) {
t.Fatalf("\nwant:\n%s\n\ngot:\n%s", out, got)
}
}

View File

@ -1,52 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package report
import (
"fmt"
)
// Parser parses the log file and generates a summary report.
type Parser struct {
summary *Summary // summarizes the parsing results
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// New returns a new parsing state.
func New() *Parser {
return &Parser{summary: newSummary()}
}
// Parse parses a log line and adds it to the summary.
func (p *Parser) Parse(line string) {
// if there was an error do not continue
if p.lerr != nil {
return
}
// chain the parser's error to the result's
res, err := parse(line)
if p.lines++; err != nil {
p.lerr = fmt.Errorf("line #%d: %s", p.lines, err)
return
}
p.summary.update(res)
}
// Summarize summarizes the parsing results.
// Only use it after the parsing is done.
func (p *Parser) Summarize() *Summary {
return p.summary
}
// Err returns the last error encountered
func (p *Parser) Err() error {
return p.lerr
}

View File

@ -1,55 +0,0 @@
package report_test
import (
"strings"
"testing"
"github.com/inancgumus/learngo/28-methods/logparser-testing/report"
)
func newParser(lines string) *report.Parser {
p := report.New()
p.Parse(lines)
return p
}
func TestParserLineErrs(t *testing.T) {
p := newParser("a.com 1 2")
p.Parse("b.com -1 -1")
want := "#2"
err := p.Err().Error()
if !strings.Contains(err, want) {
t.Errorf("want: %q; got: %q", want, err)
}
}
func TestParserStopsOnErr(t *testing.T) {
p := newParser("a.com 10 20")
p.Parse("b.com -1 -1")
p.Parse("neverparses.com 30 40")
s := p.Summarize()
if want, got := 10, s.Total().Visits; want != got {
t.Errorf("want: %d; got: %d", want, got)
}
}
func TestParserIncorrectFields(t *testing.T) {
tests := []struct {
in, name string
}{
{"a.com", "missing fields"},
{"a.com -1 2", "incorrect visits"},
{"a.com 1 -1", "incorrect time spent"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if p := newParser(tt.in); p.Err() == nil {
t.Errorf("in: %q; got: nil err", tt.in)
}
})
}
}

View File

@ -1,60 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package report
import (
"fmt"
"strconv"
"strings"
)
// always put all the related things together as in here
// Result stores metrics for a domain
// it uses the value mechanics,
// because it doesn't have to update anything
type Result struct {
Domain string `json:"domain"`
Visits int `json:"visits"`
TimeSpent int `json:"time_spent"`
// add more metrics if needed
}
// add adds the metrics of another Result to itself and returns a new Result
func (r Result) add(other Result) Result {
return Result{
Domain: r.Domain,
Visits: r.Visits + other.Visits,
TimeSpent: r.TimeSpent + other.TimeSpent,
}
}
// parse parses a single log line
func parse(line string) (r Result, err error) {
fields := strings.Fields(line)
if len(fields) != 3 {
return r, fmt.Errorf("missing fields: %v", fields)
}
f := new(field)
r.Domain = fields[0]
r.Visits = f.atoi("visits", fields[1])
r.TimeSpent = f.atoi("time spent", fields[2])
return r, f.err
}
// field helps for field parsing
type field struct{ err error }
func (f *field) atoi(name, val string) int {
n, err := strconv.Atoi(val)
if n < 0 || err != nil {
f.err = fmt.Errorf("incorrect %s: %q", name, val)
}
return n
}

View File

@ -1,86 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package report
import (
"sort"
)
// Summary aggregates the parsing results
type Summary struct {
sum map[string]Result // metrics per domain
domains []string // unique domain names
total Result // total visits for all domains
}
// newSummary constructs and initializes a new summary
// You can't use its methods without pointer mechanics
func newSummary() *Summary {
return &Summary{sum: make(map[string]Result)}
}
// Update updates the report for the given parsing result
func (s *Summary) update(r Result) {
domain := r.Domain
if _, ok := s.sum[domain]; !ok {
s.domains = append(s.domains, domain)
}
// let the result handle the addition
// this allows us to manage the result in once place
// and this way it becomes easily extendable
s.total = s.total.add(r)
s.sum[domain] = r.add(s.sum[domain])
}
// Iterator returns `next()` to detect when the iteration ends,
// and a `cur()` to return the current result.
// iterator iterates sorted by domains.
func (s *Summary) Iterator() (next func() bool, cur func() Result) {
sort.Strings(s.domains)
// remember the last iterated result
var last int
next = func() bool {
defer func() { last++ }()
return len(s.domains) > last
}
cur = func() Result {
// returns a copy so the caller cannot change it
name := s.domains[last-1]
return s.sum[name]
}
return
}
// Total returns the total metrics
func (s *Summary) Total() Result {
return s.total
}
// For the interfaces section
//
// MarshalJSON marshals a report to JSON
// Alternative: unexported embedding
// func (s *Summary) MarshalJSON() ([]byte, error) {
// type total struct {
// *Result
// IgnoreDomain *string `json:"domain,omitempty"`
// }
// return json.Marshal(struct {
// Sum map[string]Result `json:"summary"`
// Domains []string `json:"domains"`
// Total total `json:"total"`
// }{
// Sum: s.sum, Domains: s.domains, Total: total{Result: &s.total},
// })
// }

View File

@ -1,44 +0,0 @@
package report_test
import (
"testing"
"github.com/inancgumus/learngo/28-methods/logparser-testing/report"
)
func TestSummaryTotal(t *testing.T) {
p := newParser("a.com 1 2")
p.Parse("b.com 3 4")
s := p.Summarize()
want := report.Result{Domain: "", Visits: 4, TimeSpent: 6}
if got := s.Total(); want != got {
t.Errorf("want: %+v; got: %+v", want, got)
}
}
func TestSummaryIterator(t *testing.T) {
p := newParser("a.com 1 2")
p.Parse("a.com 3 4")
p.Parse("b.com 5 6")
s := p.Summarize()
next, cur := s.Iterator()
wants := []report.Result{
{Domain: "a.com", Visits: 4, TimeSpent: 6},
{Domain: "b.com", Visits: 5, TimeSpent: 6},
}
for _, want := range wants {
t.Run(want.Domain, func(t *testing.T) {
if got := next(); !got {
t.Errorf("next(): want: %t; got: %t", true, got)
}
if got := cur(); want != got {
t.Errorf("cur(): want: %+v; got: %+v", want, got)
}
})
}
}

View File

@ -1,73 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/inancgumus/learngo/29-interfaces/logparser-testing/report"
)
// summarize prints the parsing results.
//
// it prints the errors and returns if there are any.
//
// --json flag encodes to json and prints.
func summarize(sum *report.Summary, errors ...error) {
if errs(errors...) {
return
}
if args := os.Args[1:]; len(args) == 1 && args[0] == "--json" {
encode(sum)
return
}
stdout(sum)
}
// encodes the summary to json
func encode(sum *report.Summary) {
out, err := json.MarshalIndent(sum, "", "\t")
if err != nil {
panic(err)
}
os.Stdout.Write(out)
}
// prints the summary to standard out
func stdout(sum *report.Summary) {
const (
head = "%-30s %10s %20s\n"
val = "%-30s %10d %20d\n"
)
fmt.Printf(head, "DOMAIN", "VISITS", "TIME SPENT")
fmt.Println(strings.Repeat("-", 65))
for next, cur := sum.Iterator(); next(); {
r := cur()
fmt.Printf(val, r.Domain, r.Visits, r.TimeSpent)
}
t := sum.Total()
fmt.Printf("\n"+val, "TOTAL", t.Visits, t.TimeSpent)
}
// this variadic func simplifies the multiple error handling
func errs(errs ...error) (wasErr bool) {
for _, err := range errs {
if err != nil {
fmt.Printf("> Err: %s\n", err)
wasErr = true
}
}
return
}

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB