diff --git a/advfuncs/logparser/01/errdump.go b/advfuncs/logparser/01/errdump.go deleted file mode 100644 index a55bf46..0000000 --- a/advfuncs/logparser/01/errdump.go +++ /dev/null @@ -1,19 +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" - -// dumpErrs together to simplify multiple error handling -func dumpErrs(errs []error) { - for _, err := range errs { - if err != nil { - fmt.Println("> Err:", err) - } - } -} diff --git a/advfuncs/logparser/01/filters.go b/advfuncs/logparser/01/filters.go deleted file mode 100644 index 01594b3..0000000 --- a/advfuncs/logparser/01/filters.go +++ /dev/null @@ -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 "strings" - -func filter(r result) bool { - return filterOrg(r) -} - -func filterOrg(r result) bool { - return strings.HasSuffix(r.domain, ".org") -} - -func filterBlogs(r result) bool { - return strings.HasPrefix(r.domain, "blog.") -} - -// type filterFunc func(result) bool - -// func filter(r result, process filterFunc) bool { -// return process(r) -// } - -// func noopFilter(r result) bool { -// return true -// } - -// func notUsing(filter filterFunc) filterFunc { -// return func(r result) bool { -// return !filter(r) -// } -// } diff --git a/advfuncs/logparser/01/log.txt b/advfuncs/logparser/01/log.txt deleted file mode 100644 index 940b832..0000000 --- a/advfuncs/logparser/01/log.txt +++ /dev/null @@ -1,16 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 15 -learngoprogramming.com 10 -learngoprogramming.com 20 -learngoprogramming.com 5 -golang.org 40 -golang.org 20 -golang.org 45 -golang.org 15 -blog.golang.org 60 -blog.golang.org 30 -blog.golang.org 20 -blog.golang.org 65 -blog.golang.org 15 -inanc.io 30 -inanc.io 70 \ No newline at end of file diff --git a/advfuncs/logparser/01/log_err_missing.txt b/advfuncs/logparser/01/log_err_missing.txt deleted file mode 100644 index 9169c91..0000000 --- a/advfuncs/logparser/01/log_err_missing.txt +++ /dev/null @@ -1,16 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 15 -learngoprogramming.com 10 -learngoprogramming.com 20 -learngoprogramming.com -golang.org 40 -golang.org 20 -golang.org 45 -golang.org 15 -blog.golang.org 60 -blog.golang.org 30 -blog.golang.org 20 -blog.golang.org 65 -blog.golang.org 15 -inanc.io 30 -inanc.io 70 \ No newline at end of file diff --git a/advfuncs/logparser/01/log_err_negative.txt b/advfuncs/logparser/01/log_err_negative.txt deleted file mode 100644 index feccc0c..0000000 --- a/advfuncs/logparser/01/log_err_negative.txt +++ /dev/null @@ -1,16 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 15 -learngoprogramming.com 10 -learngoprogramming.com 20 -learngoprogramming.com 5 -golang.org 40 -golang.org 20 -golang.org -50 -golang.org 15 -blog.golang.org 60 -blog.golang.org 30 -blog.golang.org 20 -blog.golang.org 65 -blog.golang.org 15 -inanc.io 30 -inanc.io 70 \ No newline at end of file diff --git a/advfuncs/logparser/01/log_err_str.txt b/advfuncs/logparser/01/log_err_str.txt deleted file mode 100644 index 59a1fd8..0000000 --- a/advfuncs/logparser/01/log_err_str.txt +++ /dev/null @@ -1,16 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 15 -learngoprogramming.com 10 -learngoprogramming.com 20 -learngoprogramming.com 5 -golang.org FORTY -golang.org 20 -golang.org 45 -golang.org 15 -blog.golang.org 60 -blog.golang.org 30 -blog.golang.org 20 -blog.golang.org 65 -blog.golang.org 15 -inanc.io 30 -inanc.io 70 \ No newline at end of file diff --git a/advfuncs/logparser/01/main.go b/advfuncs/logparser/01/main.go deleted file mode 100644 index 505f5c9..0000000 --- a/advfuncs/logparser/01/main.go +++ /dev/null @@ -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" - "os" -) - -/* -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 main() { - p := newParser() - - in := bufio.NewScanner(os.Stdin) - for in.Scan() { - r := parse(p, in.Text()) // TODO: parsed -> r - update(p, r) - } - - summarize(p) - dumpErrs([]error{in.Err(), err(p)}) -} diff --git a/advfuncs/logparser/01/output.go b/advfuncs/logparser/01/output.go deleted file mode 100644 index 2a5eb17..0000000 --- a/advfuncs/logparser/01/output.go +++ /dev/null @@ -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 - -import ( - "fmt" - "sort" - "strings" -) - -// summarize the parsing results -func summarize(p *parser) { - sort.Strings(p.domains) - - fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") - fmt.Println(strings.Repeat("-", 45)) - - for _, domain := range p.domains { - fmt.Printf("%-30s %10d\n", domain, p.sum[domain].visits) - } - fmt.Printf("\n%-30s %10d\n", "TOTAL", p.total) -} diff --git a/advfuncs/logparser/01/update.go b/advfuncs/logparser/01/update.go deleted file mode 100644 index 173ee1a..0000000 --- a/advfuncs/logparser/01/update.go +++ /dev/null @@ -1,33 +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 - -// update updates all the parsing results using the given parsing result -func update(p *parser, r result) { - if p.lerr != nil { - return - } - - if !filter(r) { - return - } - - // Collect the unique domains - if _, ok := p.sum[r.domain]; !ok { - p.domains = append(p.domains, r.domain) - } - - // Keep track of total and per domain visits - p.total += r.visits - - // create and assign a new copy of `visit` - p.sum[r.domain] = result{ - domain: r.domain, - visits: r.visits + p.sum[r.domain].visits, - } -} diff --git a/advfuncs/logparser/refactor-notes/refactor-00/changes.md b/advfuncs/logparser/refactor-notes/refactor-00/changes.md deleted file mode 100644 index bf1a690..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-00/changes.md +++ /dev/null @@ -1,34 +0,0 @@ -## 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 \ No newline at end of file diff --git a/advfuncs/logparser/refactor-notes/refactor-00/parser.go b/advfuncs/logparser/refactor-notes/refactor-00/parser.go deleted file mode 100644 index d347e35..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-00/parser.go +++ /dev/null @@ -1,74 +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 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 -} diff --git a/advfuncs/logparser/refactor-notes/refactor-00/result.go b/advfuncs/logparser/refactor-notes/refactor-00/result.go deleted file mode 100644 index 83c577f..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-00/result.go +++ /dev/null @@ -1,53 +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" -) - -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 -} diff --git a/advfuncs/logparser/refactor-notes/refactor-00/summarize.go b/advfuncs/logparser/refactor-notes/refactor-00/summarize.go deleted file mode 100644 index 5d934ab..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-00/summarize.go +++ /dev/null @@ -1,43 +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" - "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) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-01/changes.md b/advfuncs/logparser/refactor-notes/refactor-01/changes.md deleted file mode 100644 index 703434f..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-01/changes.md +++ /dev/null @@ -1,61 +0,0 @@ -### 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++ - } - ``` \ No newline at end of file diff --git a/advfuncs/logparser/refactor-notes/refactor-01/main.go b/advfuncs/logparser/refactor-notes/refactor-01/main.go deleted file mode 100644 index c11aa06..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-01/main.go +++ /dev/null @@ -1,23 +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() { - p := newParser() - - if err := parse(p); err != nil { - fmt.Println("> Err:", err) - return - } - - summarize(p) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-01/parser.go b/advfuncs/logparser/refactor-notes/refactor-01/parser.go deleted file mode 100644 index a0aee50..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-01/parser.go +++ /dev/null @@ -1,57 +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" - "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]) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-01/result.go b/advfuncs/logparser/refactor-notes/refactor-01/result.go deleted file mode 100644 index 83c577f..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-01/result.go +++ /dev/null @@ -1,53 +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" -) - -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 -} diff --git a/advfuncs/logparser/refactor-notes/refactor-01/summarize.go b/advfuncs/logparser/refactor-notes/refactor-01/summarize.go deleted file mode 100644 index 1a13b48..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-01/summarize.go +++ /dev/null @@ -1,48 +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" - "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) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-02/changes.md b/advfuncs/logparser/refactor-notes/refactor-02/changes.md deleted file mode 100644 index ae7d286..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-02/changes.md +++ /dev/null @@ -1,20 +0,0 @@ -### 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()` \ No newline at end of file diff --git a/advfuncs/logparser/refactor-notes/refactor-02/parser.go b/advfuncs/logparser/refactor-notes/refactor-02/parser.go deleted file mode 100644 index 16ab798..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-02/parser.go +++ /dev/null @@ -1,48 +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" - "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() -} diff --git a/advfuncs/logparser/refactor-notes/refactor-02/result.go b/advfuncs/logparser/refactor-notes/refactor-02/result.go deleted file mode 100644 index 83c577f..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-02/result.go +++ /dev/null @@ -1,53 +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" -) - -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 -} diff --git a/advfuncs/logparser/refactor-notes/refactor-02/summarize.go b/advfuncs/logparser/refactor-notes/refactor-02/summarize.go deleted file mode 100644 index 242c760..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-02/summarize.go +++ /dev/null @@ -1,49 +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" - "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) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/changes.md b/advfuncs/logparser/refactor-notes/refactor-03-deprecated/changes.md deleted file mode 100644 index 7665010..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/changes.md +++ /dev/null @@ -1,14 +0,0 @@ -### 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 \ No newline at end of file diff --git a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/main.go b/advfuncs/logparser/refactor-notes/refactor-03-deprecated/main.go deleted file mode 100644 index ec21e96..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/main.go +++ /dev/null @@ -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" -) - -/* -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) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/parser.go b/advfuncs/logparser/refactor-notes/refactor-03-deprecated/parser.go deleted file mode 100644 index 4fb06d4..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/parser.go +++ /dev/null @@ -1,40 +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 - -// 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]) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/result.go b/advfuncs/logparser/refactor-notes/refactor-03-deprecated/result.go deleted file mode 100644 index 83c577f..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/result.go +++ /dev/null @@ -1,53 +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" -) - -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 -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/scanner.go b/advfuncs/logparser/refactor-notes/refactor-03-deprecated/scanner.go deleted file mode 100644 index fc37b36..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/scanner.go +++ /dev/null @@ -1,35 +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" - "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() -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/summarize.go b/advfuncs/logparser/refactor-notes/refactor-03-deprecated/summarize.go deleted file mode 100644 index 1a13b48..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03-deprecated/summarize.go +++ /dev/null @@ -1,48 +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" - "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) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03/changes.md b/advfuncs/logparser/refactor-notes/refactor-03/changes.md deleted file mode 100644 index d3ff67e..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03/changes.md +++ /dev/null @@ -1,7 +0,0 @@ -### PROBLEM -+ ... - -## SOLUTION -+ `parser struct` -> `pipeline struct` -+ `parse()` -> `pipe(pipeline)` - diff --git a/advfuncs/logparser/refactor-notes/refactor-03/main.go b/advfuncs/logparser/refactor-notes/refactor-03/main.go deleted file mode 100644 index b218604..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03/main.go +++ /dev/null @@ -1,29 +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() { - 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) -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03/parser.go b/advfuncs/logparser/refactor-notes/refactor-03/parser.go deleted file mode 100644 index 0e04e4b..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03/parser.go +++ /dev/null @@ -1,48 +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" - "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() -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03/result.go b/advfuncs/logparser/refactor-notes/refactor-03/result.go deleted file mode 100644 index 83c577f..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03/result.go +++ /dev/null @@ -1,53 +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" -) - -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 -} diff --git a/advfuncs/logparser/refactor-notes/refactor-03/summarize.go b/advfuncs/logparser/refactor-notes/refactor-03/summarize.go deleted file mode 100644 index 6a0ec8b..0000000 --- a/advfuncs/logparser/refactor-notes/refactor-03/summarize.go +++ /dev/null @@ -1,48 +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" - "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) -} diff --git a/advfuncs/logparser/functional/Makefile b/logparser/functional/Makefile similarity index 100% rename from advfuncs/logparser/functional/Makefile rename to logparser/functional/Makefile diff --git a/advfuncs/logparser/functional/chartwriter.go b/logparser/functional/chartwriter.go similarity index 100% rename from advfuncs/logparser/functional/chartwriter.go rename to logparser/functional/chartwriter.go diff --git a/advfuncs/logparser/functional/field.go b/logparser/functional/field.go similarity index 100% rename from advfuncs/logparser/functional/field.go rename to logparser/functional/field.go diff --git a/advfuncs/logparser/functional/filters.go b/logparser/functional/filters.go similarity index 100% rename from advfuncs/logparser/functional/filters.go rename to logparser/functional/filters.go diff --git a/advfuncs/logparser/functional/groupers.go b/logparser/functional/groupers.go similarity index 100% rename from advfuncs/logparser/functional/groupers.go rename to logparser/functional/groupers.go diff --git a/advfuncs/logparser/functional/main.go b/logparser/functional/main.go similarity index 100% rename from advfuncs/logparser/functional/main.go rename to logparser/functional/main.go diff --git a/advfuncs/logparser/functional/pipeline.go b/logparser/functional/pipeline.go similarity index 100% rename from advfuncs/logparser/functional/pipeline.go rename to logparser/functional/pipeline.go diff --git a/advfuncs/logparser/functional/result.go b/logparser/functional/result.go similarity index 100% rename from advfuncs/logparser/functional/result.go rename to logparser/functional/result.go diff --git a/advfuncs/logparser/functional/textreader.go b/logparser/functional/textreader.go similarity index 100% rename from advfuncs/logparser/functional/textreader.go rename to logparser/functional/textreader.go diff --git a/advfuncs/logparser/functional/textwriter.go b/logparser/functional/textwriter.go similarity index 100% rename from advfuncs/logparser/functional/textwriter.go rename to logparser/functional/textwriter.go diff --git a/interfaces/log-parser/logs/Makefile b/logparser/logs/Makefile similarity index 100% rename from interfaces/log-parser/logs/Makefile rename to logparser/logs/Makefile diff --git a/interfaces/log-parser/logs/log.jsonl b/logparser/logs/log.jsonl similarity index 100% rename from interfaces/log-parser/logs/log.jsonl rename to logparser/logs/log.jsonl diff --git a/interfaces/log-parser/logs/log.txt b/logparser/logs/log.txt similarity index 100% rename from interfaces/log-parser/logs/log.txt rename to logparser/logs/log.txt diff --git a/interfaces/log-parser/logs/log_err_missing.txt b/logparser/logs/log_err_missing.txt similarity index 100% rename from interfaces/log-parser/logs/log_err_missing.txt rename to logparser/logs/log_err_missing.txt diff --git a/interfaces/log-parser/logs/log_err_negative.txt b/logparser/logs/log_err_negative.txt similarity index 100% rename from interfaces/log-parser/logs/log_err_negative.txt rename to logparser/logs/log_err_negative.txt diff --git a/interfaces/log-parser/logs/log_err_str.txt b/logparser/logs/log_err_str.txt similarity index 100% rename from interfaces/log-parser/logs/log_err_str.txt rename to logparser/logs/log_err_str.txt diff --git a/interfaces/log-parser/oop/Makefile b/logparser/oop/Makefile similarity index 100% rename from interfaces/log-parser/oop/Makefile rename to logparser/oop/Makefile diff --git a/logparser/oop/chartreport.go b/logparser/oop/chartreport.go new file mode 100644 index 0000000..a77ba78 --- /dev/null +++ b/logparser/oop/chartreport.go @@ -0,0 +1,38 @@ +package main + +// You need to run: +// go get -u github.com/wcharczuk/go-chart + +// type chartReport struct { +// title string +// width, height int +// } + +// func (s *chartReport) digest(records iterator) error { +// w := os.Stdout + +// donut := chart.DonutChart{ +// Title: s.title, +// TitleStyle: chart.Style{ +// FontSize: 35, +// Show: true, +// FontColor: chart.ColorAlternateGreen, +// }, +// Width: s.width, +// Height: s.height, +// } + +// records.each(func(r record) { +// v := chart.Value{ +// Label: r.domain + r.page + ": " + strconv.Itoa(r.visits), +// Value: float64(r.visits), +// Style: chart.Style{ +// FontSize: 14, +// }, +// } + +// donut.Values = append(donut.Values, v) +// }) + +// return donut.Render(chart.SVG, w) +// } diff --git a/logparser/oop/filter.go b/logparser/oop/filter.go new file mode 100644 index 0000000..031a556 --- /dev/null +++ b/logparser/oop/filter.go @@ -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 + +type filter struct { + src iterator + filters []filterFunc +} + +func filterBy(fn ...filterFunc) *filter { + return &filter{filters: fn} +} + +// transform the record +func (f *filter) digest(records iterator) error { + f.src = records + return nil +} + +// each yields only the filtered records +func (f *filter) each(yield recordFn) error { + return f.src.each(func(r record) { + if !f.check(r) { + return + } + yield(r) + }) +} + +// check all the filters against the record +func (f *filter) check(r record) bool { + for _, fi := range f.filters { + if !fi(r) { + return false + } + } + return true +} diff --git a/logparser/oop/filters.go b/logparser/oop/filters.go new file mode 100644 index 0000000..ad00098 --- /dev/null +++ b/logparser/oop/filters.go @@ -0,0 +1,36 @@ +package main + +import "strings" + +type filterFunc func(record) bool + +func noopFilter(r record) bool { + return true +} + +func notUsing(filter filterFunc) filterFunc { + return func(r record) bool { + return !filter(r) + } +} + +func domainExtFilter(domains ...string) filterFunc { + return func(r record) bool { + for _, domain := range domains { + if strings.HasSuffix(r.domain, "."+domain) { + return true + } + } + return false + } +} + +func domainFilter(domain string) filterFunc { + return func(r record) bool { + return strings.Contains(r.domain, domain) + } +} + +func orgDomainsFilter(r record) bool { + return strings.HasSuffix(r.domain, ".org") +} diff --git a/logparser/oop/group.go b/logparser/oop/group.go new file mode 100644 index 0000000..5d57955 --- /dev/null +++ b/logparser/oop/group.go @@ -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 ( + "sort" +) + +type group struct { + sum map[string]record // metrics per group key + keys []string // unique group keys + key groupFunc +} + +func groupBy(key groupFunc) *group { + return &group{ + sum: make(map[string]record), + key: key, + } +} + +// digest the records +func (g *group) digest(records iterator) error { + return records.each(func(r record) { + k := g.key(r) + + if _, ok := g.sum[k]; !ok { + g.keys = append(g.keys, k) + } + + g.sum[k] = r.sum(g.sum[k]) + }) +} + +// each yields the grouped records +func (g *group) each(yield recordFn) error { + sort.Strings(g.keys) + + for _, k := range g.keys { + yield(g.sum[k]) + } + + return nil +} diff --git a/logparser/oop/groupers.go b/logparser/oop/groupers.go new file mode 100644 index 0000000..79980e7 --- /dev/null +++ b/logparser/oop/groupers.go @@ -0,0 +1,15 @@ +package main + +type groupFunc func(record) string + +// 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 record) string { + return r.domain +} + +func pageGrouper(r record) string { + return r.domain + r.page +} diff --git a/logparser/oop/jsonlog.go b/logparser/oop/jsonlog.go new file mode 100644 index 0000000..1c3affe --- /dev/null +++ b/logparser/oop/jsonlog.go @@ -0,0 +1,43 @@ +// For more tutorials: https://bj.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "bufio" + "encoding/json" + "io" +) + +type jsonLog struct { + reader io.Reader +} + +func newJSONLog(r io.Reader) *jsonLog { + return &jsonLog{reader: r} +} + +func (j *jsonLog) each(yield recordFn) error { + defer readClose(j.reader) + + dec := json.NewDecoder(bufio.NewReader(j.reader)) + + for { + var r record + + err := dec.Decode(&r) + if err == io.EOF { + break + } + if err != nil { + return err + } + + yield(r) + } + return nil +} diff --git a/logparser/oop/logcount.go b/logparser/oop/logcount.go new file mode 100644 index 0000000..62f214a --- /dev/null +++ b/logparser/oop/logcount.go @@ -0,0 +1,33 @@ +// 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" + +// logCount counts the yielded records +type logCount struct { + iterator + n int +} + +func (lc *logCount) each(yield recordFn) error { + err := lc.iterator.each(func(r record) { + lc.n++ + yield(r) + }) + + if err != nil { + // lc.n+1: iterator.each won't call yield on err + return fmt.Errorf("record %d: %v", lc.n+1, err) + } + return nil +} + +func (lc *logCount) count() int { + return lc.n +} diff --git a/logparser/oop/main.go b/logparser/oop/main.go new file mode 100644 index 0000000..285fdbb --- /dev/null +++ b/logparser/oop/main.go @@ -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 ( + "log" + "os" +) + +func main() { + // newGrouper(domainGrouper) + + // s := &chartReport{ + // title: "visits per domain", + // width: 1920, + // height: 800, + // } + + // pipe, err := fromFile("../logs/log.jsonl") + // if err != nil { + // log.Fatalln(err) + // } + + pipe := newPipeline( + newTextLog(os.Stdin), + // newJSONLog(os.Stdin), + newTextReport(), + filterBy(notUsing(domainExtFilter("com", "io"))), + groupBy(domainGrouper), + ) + + if err := pipe.run(); err != nil { + log.Fatalln(err) + } + + // if err := reportFromFile(os.Args[1]); err != nil { + // log.Fatalln(err) + // } +} diff --git a/logparser/oop/pipeline.go b/logparser/oop/pipeline.go new file mode 100644 index 0000000..f2d2f7c --- /dev/null +++ b/logparser/oop/pipeline.go @@ -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 ( + "fmt" + "os" + "strings" +) + +type recordFn func(record) + +type iterator interface{ each(recordFn) error } +type digester interface{ digest(iterator) error } + +type transform interface { + digester + iterator +} + +type pipeline struct { + src iterator + trans []transform + dst digester +} + +func (p *pipeline) run() error { + defer func() { + n := p.src.(*logCount).count() + fmt.Printf("%d records processed.\n", n) + }() + + last := p.src + + for _, t := range p.trans { + if err := t.digest(last); err != nil { + return err + } + last = t + } + + return p.dst.digest(last) +} + +func newPipeline(src iterator, dst digester, t ...transform) *pipeline { + return &pipeline{ + src: &logCount{iterator: src}, + dst: dst, + trans: t, + } +} + +// fromFile generates a default report +func fromFile(path string) (*pipeline, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + + var src iterator + switch { + case strings.HasSuffix(path, ".txt"): + src = newTextLog(f) + case strings.HasSuffix(path, ".jsonl"): + src = newJSONLog(f) + } + + return newPipeline( + src, + newTextReport(), + groupBy(domainGrouper), + ), nil +} diff --git a/advfuncs/logparser/refactor-notes/refactor-02/main.go b/logparser/oop/readclose.go similarity index 66% rename from advfuncs/logparser/refactor-notes/refactor-02/main.go rename to logparser/oop/readclose.go index 636834f..1aa7a30 100644 --- a/advfuncs/logparser/refactor-notes/refactor-02/main.go +++ b/logparser/oop/readclose.go @@ -8,15 +8,11 @@ package main import ( - "fmt" + "io" ) -func main() { - res, err := parse() - if err != nil { - fmt.Println("> Err:", err) - return +func readClose(r io.Reader) { + if rc, ok := r.(io.Closer); ok { + rc.Close() } - - summarize(res) } diff --git a/logparser/oop/record.go b/logparser/oop/record.go new file mode 100644 index 0000000..80b020f --- /dev/null +++ b/logparser/oop/record.go @@ -0,0 +1,82 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" +) + +const fieldsLength = 4 + +type record struct { + domain string + page string + visits int + uniques int +} + +func (r record) sum(other record) record { + r.visits += other.visits + r.uniques += other.uniques + return r +} + +// UnmarshalText to a *record +func (r *record) UnmarshalText(p []byte) (err error) { + fields := strings.Fields(string(p)) + if len(fields) != fieldsLength { + return fmt.Errorf("wrong number of fields %q", fields) + } + + r.domain, r.page = fields[0], fields[1] + + if r.visits, err = parseStr("visits", fields[2]); err != nil { + return err + } + if r.uniques, err = parseStr("uniques", fields[3]); err != nil { + return err + } + return validate(*r) +} + +// UnmarshalJSON to a *record +func (r *record) UnmarshalJSON(data []byte) error { + var re struct { + Domain string + Page string + Visits int + Uniques int + } + + if err := json.Unmarshal(data, &re); err != nil { + return err + } + + *r = record{re.Domain, re.Page, re.Visits, re.Uniques} + return validate(*r) +} + +// parseStr helps UnmarshalText for string to positive int parsing +func parseStr(name, v string) (int, error) { + n, err := strconv.Atoi(v) + if err != nil { + return 0, fmt.Errorf("record.UnmarshalText %q: %v", name, err) + } + return n, nil +} + +func validate(r record) (err error) { + switch { + case r.domain == "": + err = errors.New("record.domain cannot be empty") + case r.page == "": + err = errors.New("record.page cannot be empty") + case r.visits < 0: + err = errors.New("record.visits cannot be negative") + case r.uniques < 0: + err = errors.New("record.uniques cannot be negative") + } + return +} diff --git a/logparser/oop/textlog.go b/logparser/oop/textlog.go new file mode 100644 index 0000000..9a5e1fa --- /dev/null +++ b/logparser/oop/textlog.go @@ -0,0 +1,39 @@ +// 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" + "io" +) + +type textLog struct { + reader io.Reader +} + +func newTextLog(r io.Reader) *textLog { + return &textLog{reader: r} +} + +func (p *textLog) each(yield recordFn) error { + defer readClose(p.reader) + + in := bufio.NewScanner(p.reader) + + for in.Scan() { + r := new(record) + + if err := r.UnmarshalText(in.Bytes()); err != nil { + return err + } + + yield(*r) + } + + return in.Err() +} diff --git a/logparser/oop/textreport.go b/logparser/oop/textreport.go new file mode 100644 index 0000000..b0c42de --- /dev/null +++ b/logparser/oop/textreport.go @@ -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" + "os" + "text/tabwriter" +) + +// TODO: make this configurable? or exercise? +const ( + minWidth = 0 + tabWidth = 4 + padding = 4 + flags = 0 +) + +type textReport struct{} + +func newTextReport() *textReport { + return new(textReport) +} + +func (s *textReport) digest(records 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 record + records.each(func(r record) { + total = total.sum(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() +} diff --git a/interfaces/log-parser/testing/log.txt b/logparser/testing/log.txt similarity index 100% rename from interfaces/log-parser/testing/log.txt rename to logparser/testing/log.txt diff --git a/interfaces/log-parser/testing/log_err_missing.txt b/logparser/testing/log_err_missing.txt similarity index 100% rename from interfaces/log-parser/testing/log_err_missing.txt rename to logparser/testing/log_err_missing.txt diff --git a/interfaces/log-parser/testing/log_err_negative.txt b/logparser/testing/log_err_negative.txt similarity index 100% rename from interfaces/log-parser/testing/log_err_negative.txt rename to logparser/testing/log_err_negative.txt diff --git a/interfaces/log-parser/testing/log_err_str.txt b/logparser/testing/log_err_str.txt similarity index 100% rename from interfaces/log-parser/testing/log_err_str.txt rename to logparser/testing/log_err_str.txt diff --git a/interfaces/log-parser/testing/main.go b/logparser/testing/main.go similarity index 84% rename from interfaces/log-parser/testing/main.go rename to logparser/testing/main.go index 5bbd2b4..a149502 100644 --- a/interfaces/log-parser/testing/main.go +++ b/logparser/testing/main.go @@ -11,7 +11,7 @@ import ( "bufio" "os" - "github.com/inancgumus/learngo/interfaces/log-parser/testing/report" + "github.com/inancgumus/learngo/logparser/testing/report" ) func main() { diff --git a/interfaces/log-parser/testing/main_test.go b/logparser/testing/main_test.go similarity index 100% rename from interfaces/log-parser/testing/main_test.go rename to logparser/testing/main_test.go diff --git a/interfaces/log-parser/testing/report/parser.go b/logparser/testing/report/parser.go similarity index 100% rename from interfaces/log-parser/testing/report/parser.go rename to logparser/testing/report/parser.go diff --git a/interfaces/log-parser/testing/report/parser_test.go b/logparser/testing/report/parser_test.go similarity index 93% rename from interfaces/log-parser/testing/report/parser_test.go rename to logparser/testing/report/parser_test.go index 38a0f36..fb48262 100644 --- a/interfaces/log-parser/testing/report/parser_test.go +++ b/logparser/testing/report/parser_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/inancgumus/learngo/interfaces/log-parser/testing/report" + "github.com/inancgumus/learngo/logparser/testing/report" ) func newParser(lines string) *report.Parser { diff --git a/interfaces/log-parser/testing/report/result.go b/logparser/testing/report/result.go similarity index 100% rename from interfaces/log-parser/testing/report/result.go rename to logparser/testing/report/result.go diff --git a/interfaces/log-parser/testing/report/summary.go b/logparser/testing/report/summary.go similarity index 100% rename from interfaces/log-parser/testing/report/summary.go rename to logparser/testing/report/summary.go diff --git a/interfaces/log-parser/testing/report/summary_test.go b/logparser/testing/report/summary_test.go similarity index 92% rename from interfaces/log-parser/testing/report/summary_test.go rename to logparser/testing/report/summary_test.go index bddd3ef..55db731 100644 --- a/interfaces/log-parser/testing/report/summary_test.go +++ b/logparser/testing/report/summary_test.go @@ -3,7 +3,7 @@ package report_test import ( "testing" - "github.com/inancgumus/learngo/interfaces/log-parser/testing/report" + "github.com/inancgumus/learngo/logparser/testing/report" ) func TestSummaryTotal(t *testing.T) { diff --git a/interfaces/log-parser/testing/summarize.go b/logparser/testing/summarize.go similarity index 95% rename from interfaces/log-parser/testing/summarize.go rename to logparser/testing/summarize.go index 1b5bd06..1bb929c 100644 --- a/interfaces/log-parser/testing/summarize.go +++ b/logparser/testing/summarize.go @@ -13,7 +13,7 @@ import ( "os" "strings" - "github.com/inancgumus/learngo/interfaces/log-parser/testing/report" + "github.com/inancgumus/learngo/logparser/testing/report" ) // summarize prints the parsing results. diff --git a/logparser/v1/log.txt b/logparser/v1/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/logparser/v1/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v1/log_err_missing.txt b/logparser/v1/log_err_missing.txt new file mode 100644 index 0000000..fd8eff4 --- /dev/null +++ b/logparser/v1/log_err_missing.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v1/log_err_negative.txt b/logparser/v1/log_err_negative.txt new file mode 100644 index 0000000..60485c0 --- /dev/null +++ b/logparser/v1/log_err_negative.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org -100 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v1/log_err_str.txt b/logparser/v1/log_err_str.txt new file mode 100644 index 0000000..3a55bd7 --- /dev/null +++ b/logparser/v1/log_err_str.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org FOUR +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v1/main.go b/logparser/v1/main.go new file mode 100644 index 0000000..1fb6738 --- /dev/null +++ b/logparser/v1/main.go @@ -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 ( + "bufio" + "fmt" + "os" + "sort" + "strconv" + "strings" +) + +func main() { + var ( + sum map[string]int // total visits per domain + domains []string // unique domain names + total int // total visits to all domains + lines int // number of parsed lines (for the error messages) + ) + + sum = make(map[string]int) + + // Scan the standard-in line by line + in := bufio.NewScanner(os.Stdin) + for in.Scan() { + lines++ + + // Parse the fields + fields := strings.Fields(in.Text()) + if len(fields) != 2 { + fmt.Printf("wrong input: %v (line #%d)\n", fields, lines) + return + } + + domain := fields[0] + + // Sum the total visits per domain + visits, err := strconv.Atoi(fields[1]) + if visits < 0 || err != nil { + fmt.Printf("wrong input: %q (line #%d)\n", fields[1], lines) + return + } + + // Collect the unique domains + if _, ok := sum[domain]; !ok { + domains = append(domains, domain) + } + + // Keep track of total and per domain visits + total += visits + sum[domain] += visits + } + + // Print the visits per domain + sort.Strings(domains) + + fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") + fmt.Println(strings.Repeat("-", 45)) + + for _, domain := range domains { + visits := sum[domain] + fmt.Printf("%-30s %10d\n", domain, visits) + } + + // Print the total visits for all domains + fmt.Printf("\n%-30s %10d\n", "TOTAL", total) + + // Let's handle the error + if err := in.Err(); err != nil { + fmt.Println("> Err:", err) + } +} diff --git a/logparser/v2/log.txt b/logparser/v2/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/logparser/v2/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v2/log_err_missing.txt b/logparser/v2/log_err_missing.txt new file mode 100644 index 0000000..fd8eff4 --- /dev/null +++ b/logparser/v2/log_err_missing.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v2/log_err_negative.txt b/logparser/v2/log_err_negative.txt new file mode 100644 index 0000000..60485c0 --- /dev/null +++ b/logparser/v2/log_err_negative.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org -100 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v2/log_err_str.txt b/logparser/v2/log_err_str.txt new file mode 100644 index 0000000..3a55bd7 --- /dev/null +++ b/logparser/v2/log_err_str.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org FOUR +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v2/main.go b/logparser/v2/main.go new file mode 100644 index 0000000..df4bc1f --- /dev/null +++ b/logparser/v2/main.go @@ -0,0 +1,94 @@ +// 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" + "sort" + "strconv" + "strings" +) + +// result stores the parsed result for a domain +type result struct { + domain string + visits int + // add more metrics if needed +} + +// parser keep tracks of the parsing +type parser struct { + sum map[string]result // metrics per domain + domains []string // unique domain names + total int // total visits for all domains + lines int // number of parsed lines (for the error messages) +} + +func main() { + p := parser{sum: make(map[string]result)} + + // Scan the standard-in line by line + in := bufio.NewScanner(os.Stdin) + for in.Scan() { + p.lines++ + + // Parse the fields + fields := strings.Fields(in.Text()) + if len(fields) != 2 { + fmt.Printf("wrong input: %v (line #%d)\n", fields, p.lines) + return + } + + domain := fields[0] + + // Sum the total visits per domain + visits, err := strconv.Atoi(fields[1]) + if visits < 0 || err != nil { + fmt.Printf("wrong input: %q (line #%d)\n", fields[1], p.lines) + return + } + + // Collect the unique domains + if _, ok := p.sum[domain]; !ok { + p.domains = append(p.domains, domain) + } + + // Keep track of total and per domain visits + p.total += visits + + // You cannot assign to composite values + // p.sum[domain].visits += visits + + // create and assign a new copy of `visit` + p.sum[domain] = result{ + domain: domain, + visits: visits + p.sum[domain].visits, + } + } + + // Print the visits per domain + sort.Strings(p.domains) + + fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") + fmt.Println(strings.Repeat("-", 45)) + + for _, domain := range p.domains { + parsed := p.sum[domain] + fmt.Printf("%-30s %10d\n", domain, parsed.visits) + } + + // Print the total visits for all domains + fmt.Printf("\n%-30s %10d\n", "TOTAL", p.total) + + // Let's handle the error + if err := in.Err(); err != nil { + fmt.Println("> Err:", err) + } +} diff --git a/logparser/v3/log.txt b/logparser/v3/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/logparser/v3/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v3/log_err_missing.txt b/logparser/v3/log_err_missing.txt new file mode 100644 index 0000000..fd8eff4 --- /dev/null +++ b/logparser/v3/log_err_missing.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v3/log_err_negative.txt b/logparser/v3/log_err_negative.txt new file mode 100644 index 0000000..60485c0 --- /dev/null +++ b/logparser/v3/log_err_negative.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org -100 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v3/log_err_str.txt b/logparser/v3/log_err_str.txt new file mode 100644 index 0000000..3a55bd7 --- /dev/null +++ b/logparser/v3/log_err_str.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org FOUR +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v3/main.go b/logparser/v3/main.go new file mode 100644 index 0000000..a71d164 --- /dev/null +++ b/logparser/v3/main.go @@ -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 ( + "bufio" + "fmt" + "os" + "sort" + "strings" +) + +func main() { + p := newParser() + + // Scan the standard-in line by line + in := bufio.NewScanner(os.Stdin) + for in.Scan() { + p.lines++ + + parsed, err := parse(p, in.Text()) + if err != nil { + fmt.Println(err) + return + } + + p = update(p, parsed) + } + + // Print the visits per domain + sort.Strings(p.domains) + + fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") + fmt.Println(strings.Repeat("-", 45)) + + for _, domain := range p.domains { + parsed := p.sum[domain] + fmt.Printf("%-30s %10d\n", domain, parsed.visits) + } + + // Print the total visits for all domains + fmt.Printf("\n%-30s %10d\n", "TOTAL", p.total) + + // Let's handle the error + if err := in.Err(); err != nil { + fmt.Println("> Err:", err) + } +} diff --git a/logparser/v3/parser.go b/logparser/v3/parser.go new file mode 100644 index 0000000..9b402c9 --- /dev/null +++ b/logparser/v3/parser.go @@ -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" + "strconv" + "strings" +) + +// result stores the parsed result for a domain +type result struct { + domain string + visits int + // add more metrics if needed +} + +// parser keep tracks of the parsing +type parser struct { + sum map[string]result // metrics per domain + domains []string // unique domain names + total int // total visits for all domains + lines int // number of parsed lines (for the error messages) +} + +// newParser constructs, initializes and returns a new parser +func newParser() parser { + return parser{sum: make(map[string]result)} +} + +// parse parses a log line and returns the parsed result with an error +func parse(p parser, line string) (parsed result, err error) { + fields := strings.Fields(line) + if len(fields) != 2 { + err = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) + return + } + + parsed.domain = fields[0] + + parsed.visits, err = strconv.Atoi(fields[1]) + if parsed.visits < 0 || err != nil { + err = fmt.Errorf("wrong input: %q (line #%d)", fields[1], p.lines) + return + } + + return +} + +// update updates the parser for the given parsing result +func update(p parser, parsed result) parser { + domain, visits := parsed.domain, parsed.visits + + // Collect the unique domains + if _, ok := p.sum[domain]; !ok { + p.domains = append(p.domains, domain) + } + + // Keep track of total and per domain visits + p.total += visits + + // create and assign a new copy of `visit` + p.sum[domain] = result{ + domain: domain, + visits: visits + p.sum[domain].visits, + } + + return p +} diff --git a/logparser/v4/log.txt b/logparser/v4/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/logparser/v4/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v4/log_err_missing.txt b/logparser/v4/log_err_missing.txt new file mode 100644 index 0000000..fd8eff4 --- /dev/null +++ b/logparser/v4/log_err_missing.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v4/log_err_negative.txt b/logparser/v4/log_err_negative.txt new file mode 100644 index 0000000..60485c0 --- /dev/null +++ b/logparser/v4/log_err_negative.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org -100 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/logparser/v4/log_err_str.txt b/logparser/v4/log_err_str.txt new file mode 100644 index 0000000..3a55bd7 --- /dev/null +++ b/logparser/v4/log_err_str.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org FOUR +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/advfuncs/logparser/refactor-notes/refactor-00/main.go b/logparser/v4/main.go similarity index 62% rename from advfuncs/logparser/refactor-notes/refactor-00/main.go rename to logparser/v4/main.go index 43e763f..8616962 100644 --- a/advfuncs/logparser/refactor-notes/refactor-00/main.go +++ b/logparser/v4/main.go @@ -11,6 +11,8 @@ import ( "bufio" "fmt" "os" + "sort" + "strings" ) func main() { @@ -26,6 +28,19 @@ func main() { dumpErrs([]error{in.Err(), err(p)}) } +// summarize summarizes and prints the parsing result +func summarize(p *parser) { + sort.Strings(p.domains) + + fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") + fmt.Println(strings.Repeat("-", 45)) + + for _, domain := range p.domains { + fmt.Printf("%-30s %10d\n", domain, p.sum[domain].visits) + } + fmt.Printf("\n%-30s %10d\n", "TOTAL", p.total) +} + // dumpErrs simplifies handling multiple errors func dumpErrs(errs []error) { for _, err := range errs { diff --git a/advfuncs/logparser/01/parser.go b/logparser/v4/parser.go similarity index 64% rename from advfuncs/logparser/01/parser.go rename to logparser/v4/parser.go index 17bf22e..db7cd6a 100644 --- a/advfuncs/logparser/01/parser.go +++ b/logparser/v4/parser.go @@ -13,11 +13,11 @@ import ( "strings" ) -// result stores details about a log line +// result stores the parsed result for a domain type result struct { domain string visits int - // add more metrics when needed + // add more metrics if needed } // parser keep tracks of the parsing @@ -29,12 +29,12 @@ type parser struct { lerr error // the last error occurred } -// newParser creates and returns a new parser +// newParser constructs, initializes and returns a new parser func newParser() *parser { return &parser{sum: make(map[string]result)} } -// update the parsing results +// parse parses a log line and returns the parsed result with an error func parse(p *parser, line string) (r result) { if p.lerr != nil { return @@ -59,6 +59,27 @@ func parse(p *parser, line string) (r result) { return } +// update updates all the parsing results using the given parsing result +func update(p *parser, r result) { + if p.lerr != nil { + return + } + + // Collect the unique domains + if _, ok := p.sum[r.domain]; !ok { + p.domains = append(p.domains, r.domain) + } + + // Keep track of total and per domain visits + p.total += r.visits + + // create and assign a new copy of `visit` + p.sum[r.domain] = result{ + domain: r.domain, + visits: r.visits + p.sum[r.domain].visits, + } +} + // err returns the last error encountered func err(p *parser) error { return p.lerr diff --git a/logparser/v5/Makefile b/logparser/v5/Makefile new file mode 100644 index 0000000..a9f738d --- /dev/null +++ b/logparser/v5/Makefile @@ -0,0 +1,5 @@ +r: + go run . < ../../logs/log.txt + +t: + time go run . < ../../logs/log.txt \ No newline at end of file diff --git a/interfaces/log-parser/oop/filepipe.go b/logparser/v5/filepipe.go similarity index 73% rename from interfaces/log-parser/oop/filepipe.go rename to logparser/v5/filepipe.go index 557fb9a..f995aa4 100644 --- a/interfaces/log-parser/oop/filepipe.go +++ b/logparser/v5/filepipe.go @@ -11,10 +11,10 @@ import ( "os" "strings" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/group" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/parse" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/report" + "github.com/inancgumus/learngo/logparser/v5/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe/group" + "github.com/inancgumus/learngo/logparser/v5/pipe/parse" + "github.com/inancgumus/learngo/logparser/v5/pipe/report" ) // fromFile generates a default pipeline. diff --git a/interfaces/log-parser/oop/main.go b/logparser/v5/main.go similarity index 68% rename from interfaces/log-parser/oop/main.go rename to logparser/v5/main.go index 7a30be0..e9abd48 100644 --- a/interfaces/log-parser/oop/main.go +++ b/logparser/v5/main.go @@ -11,11 +11,11 @@ import ( "log" "os" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/filter" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/group" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/parse" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe/report" + "github.com/inancgumus/learngo/logparser/v5/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe/filter" + "github.com/inancgumus/learngo/logparser/v5/pipe/group" + "github.com/inancgumus/learngo/logparser/v5/pipe/parse" + "github.com/inancgumus/learngo/logparser/v5/pipe/report" ) func main() { diff --git a/interfaces/log-parser/oop/pipe/filter/domain.go b/logparser/v5/pipe/filter/domain.go similarity index 92% rename from interfaces/log-parser/oop/pipe/filter/domain.go rename to logparser/v5/pipe/filter/domain.go index e3901c5..5b76942 100644 --- a/interfaces/log-parser/oop/pipe/filter/domain.go +++ b/logparser/v5/pipe/filter/domain.go @@ -10,7 +10,7 @@ package filter import ( "strings" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe" ) // DomainExt filters a set of domain extensions. diff --git a/interfaces/log-parser/oop/pipe/filter/filter.go b/logparser/v5/pipe/filter/filter.go similarity index 93% rename from interfaces/log-parser/oop/pipe/filter/filter.go rename to logparser/v5/pipe/filter/filter.go index c58bab0..7a9228f 100644 --- a/interfaces/log-parser/oop/pipe/filter/filter.go +++ b/logparser/v5/pipe/filter/filter.go @@ -7,7 +7,7 @@ package filter -import "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" +import "github.com/inancgumus/learngo/logparser/v5/pipe" // Func represents a filtering pipeline func. type Func func(pipe.Record) (pass bool) diff --git a/interfaces/log-parser/oop/pipe/filter/noop.go b/logparser/v5/pipe/filter/noop.go similarity index 80% rename from interfaces/log-parser/oop/pipe/filter/noop.go rename to logparser/v5/pipe/filter/noop.go index cce6639..571587f 100644 --- a/interfaces/log-parser/oop/pipe/filter/noop.go +++ b/logparser/v5/pipe/filter/noop.go @@ -7,7 +7,7 @@ package filter -import "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" +import "github.com/inancgumus/learngo/logparser/v5/pipe" // Noop filter that does nothing. func Noop(r pipe.Record) bool { diff --git a/interfaces/log-parser/oop/pipe/filter/not.go b/logparser/v5/pipe/filter/not.go similarity index 83% rename from interfaces/log-parser/oop/pipe/filter/not.go rename to logparser/v5/pipe/filter/not.go index face064..0ddce83 100644 --- a/interfaces/log-parser/oop/pipe/filter/not.go +++ b/logparser/v5/pipe/filter/not.go @@ -7,7 +7,7 @@ package filter -import "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" +import "github.com/inancgumus/learngo/logparser/v5/pipe" // Not reverses a filter. True becomes false, and vice versa. func Not(filter Func) Func { diff --git a/interfaces/log-parser/oop/pipe/group/domain.go b/logparser/v5/pipe/group/domain.go similarity index 86% rename from interfaces/log-parser/oop/pipe/group/domain.go rename to logparser/v5/pipe/group/domain.go index 6bf650e..a99d0d1 100644 --- a/interfaces/log-parser/oop/pipe/group/domain.go +++ b/logparser/v5/pipe/group/domain.go @@ -7,7 +7,7 @@ package group -import "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" +import "github.com/inancgumus/learngo/logparser/v5/pipe" // Domain groups the records by domain. // It keeps the other fields intact. diff --git a/interfaces/log-parser/oop/pipe/group/group.go b/logparser/v5/pipe/group/group.go similarity index 94% rename from interfaces/log-parser/oop/pipe/group/group.go rename to logparser/v5/pipe/group/group.go index aed72fe..4f342cd 100644 --- a/interfaces/log-parser/oop/pipe/group/group.go +++ b/logparser/v5/pipe/group/group.go @@ -10,7 +10,7 @@ package group import ( "sort" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe" ) // Func represents a grouping func that returns a grouping key. diff --git a/interfaces/log-parser/oop/pipe/group/page.go b/logparser/v5/pipe/group/page.go similarity index 81% rename from interfaces/log-parser/oop/pipe/group/page.go rename to logparser/v5/pipe/group/page.go index 1428d2e..75ae652 100644 --- a/interfaces/log-parser/oop/pipe/group/page.go +++ b/logparser/v5/pipe/group/page.go @@ -7,7 +7,7 @@ package group -import "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" +import "github.com/inancgumus/learngo/logparser/v5/pipe" // Page groups records by page. func Page(r pipe.Record) string { diff --git a/interfaces/log-parser/oop/pipe/logcount.go b/logparser/v5/pipe/logcount.go similarity index 100% rename from interfaces/log-parser/oop/pipe/logcount.go rename to logparser/v5/pipe/logcount.go diff --git a/interfaces/log-parser/oop/pipe/parse/close.go b/logparser/v5/pipe/parse/close.go similarity index 100% rename from interfaces/log-parser/oop/pipe/parse/close.go rename to logparser/v5/pipe/parse/close.go diff --git a/interfaces/log-parser/oop/pipe/parse/json.go b/logparser/v5/pipe/parse/json.go similarity index 91% rename from interfaces/log-parser/oop/pipe/parse/json.go rename to logparser/v5/pipe/parse/json.go index 96c214e..0de8aa7 100644 --- a/interfaces/log-parser/oop/pipe/parse/json.go +++ b/logparser/v5/pipe/parse/json.go @@ -11,7 +11,7 @@ import ( "encoding/json" "io" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe" ) // JSON parses json records. diff --git a/interfaces/log-parser/oop/pipe/parse/record.go b/logparser/v5/pipe/parse/record.go similarity index 97% rename from interfaces/log-parser/oop/pipe/parse/record.go rename to logparser/v5/pipe/parse/record.go index bcc16a3..7ff330e 100644 --- a/interfaces/log-parser/oop/pipe/parse/record.go +++ b/logparser/v5/pipe/parse/record.go @@ -7,7 +7,7 @@ import ( "strconv" "strings" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe" ) const fieldsLength = 4 diff --git a/interfaces/log-parser/oop/pipe/parse/text.go b/logparser/v5/pipe/parse/text.go similarity index 91% rename from interfaces/log-parser/oop/pipe/parse/text.go rename to logparser/v5/pipe/parse/text.go index c106c86..b22d3eb 100644 --- a/interfaces/log-parser/oop/pipe/parse/text.go +++ b/logparser/v5/pipe/parse/text.go @@ -11,7 +11,7 @@ import ( "bufio" "io" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe" ) // Text parses text based log lines. diff --git a/interfaces/log-parser/oop/pipe/pipe.go b/logparser/v5/pipe/pipe.go similarity index 100% rename from interfaces/log-parser/oop/pipe/pipe.go rename to logparser/v5/pipe/pipe.go diff --git a/interfaces/log-parser/oop/pipe/pipeline.go b/logparser/v5/pipe/pipeline.go similarity index 100% rename from interfaces/log-parser/oop/pipe/pipeline.go rename to logparser/v5/pipe/pipeline.go diff --git a/interfaces/log-parser/oop/pipe/record.go b/logparser/v5/pipe/record.go similarity index 100% rename from interfaces/log-parser/oop/pipe/record.go rename to logparser/v5/pipe/record.go diff --git a/interfaces/log-parser/oop/pipe/report/chart.go b/logparser/v5/pipe/report/chart.go similarity index 100% rename from interfaces/log-parser/oop/pipe/report/chart.go rename to logparser/v5/pipe/report/chart.go diff --git a/interfaces/log-parser/oop/pipe/report/text.go b/logparser/v5/pipe/report/text.go similarity index 94% rename from interfaces/log-parser/oop/pipe/report/text.go rename to logparser/v5/pipe/report/text.go index 5db9b81..0dbf20a 100644 --- a/interfaces/log-parser/oop/pipe/report/text.go +++ b/logparser/v5/pipe/report/text.go @@ -12,7 +12,7 @@ import ( "io" "text/tabwriter" - "github.com/inancgumus/learngo/interfaces/log-parser/oop/pipe" + "github.com/inancgumus/learngo/logparser/v5/pipe" ) const ( diff --git a/x-tba/project-pricings/main.go b/x-tba/project-pricings/main.go deleted file mode 100644 index 4185931..0000000 --- a/x-tba/project-pricings/main.go +++ /dev/null @@ -1,32 +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 - -// Remember: This should point to the directory exactly after GOPATH -// Use / not \ even on Windows -import ( - "fmt" -) - -func main() { - // TODO: funcs can call other funcs - - const data = `New York,100,2,1,100000 -Hong Kong,150,3,2,300000 -Paris,200,4,3,250000 -Istanbul,500,10,5,four hundred thousand` - - props, err := parse(data) - if err != nil { - fmt.Printf("> err: %s\n\n", err) - } - - show(props) - - // fmt.Printf("%#v\n", props.list) -} diff --git a/x-tba/project-pricings/parse.go b/x-tba/project-pricings/parse.go deleted file mode 100644 index f6c6f1a..0000000 --- a/x-tba/project-pricings/parse.go +++ /dev/null @@ -1,84 +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 ( - "strconv" - "strings" -) - -const separator = "," - -// Parse parses the given data and returns a slice of Properties -func parse(data string) (props []property, err error) { - rows := strings.Split(data, "\n") - - for _, row := range rows { - cols := strings.Split(row, separator) - - loc := cols[0] - - // size, err := strconv.Atoi(cols[1]) - // if err != nil { - // return props, err - // } - - // beds, err := strconv.Atoi(cols[2]) - // if err != nil { - // return props, err - // } - - // baths, err := strconv.Atoi(cols[3]) - // if err != nil { - // return props, err - // } - - // price, err := strconv.Atoi(cols[4]) - // if err != nil { - // return props, err - // } - - size, err := atoi(cols[1], err) - beds, err := atoi(cols[2], err) - baths, err := atoi(cols[3], err) - price, err := atoi(cols[4], err) - - if err != nil { - return props, err - } - - prop := property{ - location: loc, - size: size, - beds: beds, - baths: baths, - price: price, - } - - props = append(props, prop) - } - return -} - -// atoi is a helper for strconv.Atoi -// it saves the previous error to simplify the error handling. -// usage: -// n, err := atoi(p, err) -// m, err := atoi(q, err) -func atoi(s string, err error) (int, error) { - // if there was an error return it instead: skip the Atoi - if err != nil { - return 0, err - } - - n, lerr := strconv.Atoi(s) - if lerr != nil { - return 0, lerr - } - return n, nil -} diff --git a/x-tba/project-pricings/parse_test.go b/x-tba/project-pricings/parse_test.go deleted file mode 100644 index 8ad3938..0000000 --- a/x-tba/project-pricings/parse_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "reflect" - "testing" -) - -func TestParse(t *testing.T) { - const data = `New York,100,2,1,100000 -Istanbul,150,3,2,200000` - - props := parse(data) - - if l := len(props); l != 2 { - t.Fatalf(`got: %d; want: 2`, l) - } - - want := Property{ - Location: "New York", - Size: 100, Beds: 2, Baths: 1, Price: 100000, - } - p := props[0] - - if !reflect.DeepEqual(want, p) { - t.Errorf(`got: %#v; want: %#v`, p, want) - } - - want = Property{ - Location: "Istanbul", - Size: 150, Beds: 3, Baths: 2, Price: 200000, - } - - p = props[1] - if !reflect.DeepEqual(want, p) { - t.Errorf(`got: %#v; want: %#v`, p, want) - } -} - -func TestParseBetter(t *testing.T) { - const data = `New York,100,2,1,100000 -Istanbul,150,3,2,200000` - - tests := []Property{ - { - Location: "New York", - Size: 100, Beds: 2, Baths: 1, Price: 100000, - }, - { - Location: "Istanbul", - Size: 150, Beds: 3, Baths: 2, Price: 200000, - }, - } - - props := parse(data) - - if l := len(props); l != 2 { - t.Fatalf(`got: %d; want: 2`, l) - } - - for i, p := range tests { - if !reflect.DeepEqual(p, props[i]) { - t.Errorf(`got: %#v; want: %#v`, props[i], p) - } - } -} diff --git a/x-tba/project-pricings/print.go b/x-tba/project-pricings/print.go deleted file mode 100644 index 0502aaa..0000000 --- a/x-tba/project-pricings/print.go +++ /dev/null @@ -1,37 +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" -) - -// show shows the given properties -func show(props []property) { - showHeader() - - for _, p := range props { - fmt.Printf("%-15s", p.location) - fmt.Printf("%-15d", p.size) - fmt.Printf("%-15d", p.beds) - fmt.Printf("%-15d", p.baths) - fmt.Printf("%-15d", p.price) - fmt.Println() - } -} - -// showHeader prints the header -func showHeader() { - const header = "Location,Size,Beds,Baths,Price" - - for _, h := range strings.Split(header, separator) { - fmt.Printf("%-15s", h) - } - fmt.Printf("\n%s\n", strings.Repeat("=", 75)) -} diff --git a/x-tba/project-pricings/property.go b/x-tba/project-pricings/property.go deleted file mode 100644 index e779ba8..0000000 --- a/x-tba/project-pricings/property.go +++ /dev/null @@ -1,14 +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 - -// property stores data about a property -type property struct { - location string - size, beds, baths, price int -} diff --git a/x-tba/tictactoe-experiments/01-without-funcs/main.go b/x-tba/tictactoe-experiments/01-without-funcs/main.go index 4bc59db..a481da9 100644 --- a/x-tba/tictactoe-experiments/01-without-funcs/main.go +++ b/x-tba/tictactoe-experiments/01-without-funcs/main.go @@ -145,6 +145,6 @@ func main() { } if err := ioutil.WriteFile("replay.log", inputs, 0644); err != nil { - fmt.Fprintf(os.Stderr, "Cannot save replay: ", err) + fmt.Fprintf(os.Stderr, "Cannot save replay: %v", err) } }