refactor: logparser v6
This commit is contained in:
@ -21,11 +21,6 @@ func CountRecords(p Parser) *Count {
|
|||||||
return &Count{Parser: p}
|
return &Count{Parser: p}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last counted record number.
|
|
||||||
func (c *Count) Last() int {
|
|
||||||
return c.count - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse increments the counter.
|
// Parse increments the counter.
|
||||||
func (c *Count) Parse() bool {
|
func (c *Count) Parse() bool {
|
||||||
c.count++
|
c.count++
|
||||||
@ -34,8 +29,9 @@ func (c *Count) Parse() bool {
|
|||||||
|
|
||||||
// Err returns the first error that was encountered by the Log.
|
// Err returns the first error that was encountered by the Log.
|
||||||
func (c *Count) Err() (err error) {
|
func (c *Count) Err() (err error) {
|
||||||
if err = c.Parser.Err(); err != nil {
|
err = c.Parser.Err()
|
||||||
err = fmt.Errorf("record #%d: %v", c.Last()+1, err)
|
if err != nil {
|
||||||
|
err = fmt.Errorf("record #%d: %v", c.count, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
@ -10,20 +10,22 @@ package parse
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/inancgumus/learngo/logparser/v6/logly/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSONParser parses json records.
|
// JSONParser parses json records.
|
||||||
type JSONParser struct {
|
type JSONParser struct {
|
||||||
in *json.Decoder
|
in *json.Decoder
|
||||||
err error // last error
|
err error // last error
|
||||||
last *Record // last parsed record
|
last *record.Record // last parsed record
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON creates a json parser.
|
// JSON creates a json parser.
|
||||||
func JSON(r io.Reader) *JSONParser {
|
func JSON(r io.Reader) *JSONParser {
|
||||||
return &JSONParser{
|
return &JSONParser{
|
||||||
in: json.NewDecoder(r),
|
in: json.NewDecoder(r),
|
||||||
last: new(Record),
|
last: new(record.Record),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +36,7 @@ func (p *JSONParser) Parse() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.last.Reset()
|
p.last.Reset()
|
||||||
|
|
||||||
err := p.in.Decode(&p.last)
|
err := p.in.Decode(&p.last)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return false
|
return false
|
||||||
@ -45,7 +48,7 @@ func (p *JSONParser) Parse() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Value returns the most recent record parsed by a call to Parse.
|
// Value returns the most recent record parsed by a call to Parse.
|
||||||
func (p *JSONParser) Value() Record {
|
func (p *JSONParser) Value() record.Record {
|
||||||
return *p.last
|
return *p.last
|
||||||
}
|
}
|
||||||
|
|
@ -7,13 +7,15 @@
|
|||||||
|
|
||||||
package parse
|
package parse
|
||||||
|
|
||||||
|
import "github.com/inancgumus/learngo/logparser/v6/logly/record"
|
||||||
|
|
||||||
// Parser is an interface for the parsers.
|
// Parser is an interface for the parsers.
|
||||||
type Parser interface {
|
type Parser interface {
|
||||||
// Parse the next record from the source.
|
// Parse the next record from the source.
|
||||||
Parse() bool
|
Parse() bool
|
||||||
|
|
||||||
// Value returns the last parsed record by a call to Parse.
|
// Value returns the last parsed record by a call to Parse.
|
||||||
Value() Record
|
Value() record.Record
|
||||||
|
|
||||||
// Err returns the first error that was encountered.
|
// Err returns the first error that was encountered.
|
||||||
Err() error
|
Err() error
|
@ -10,35 +10,41 @@ package parse
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/inancgumus/learngo/logparser/v6/logly/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TextParser parses text based log lines.
|
// TextParser parses text based log lines.
|
||||||
type TextParser struct {
|
type TextParser struct {
|
||||||
in *bufio.Scanner
|
in *bufio.Scanner
|
||||||
err error // last error
|
err error // last error
|
||||||
last *Record // last parsed record
|
last *record.Record // last parsed record
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text creates a text parser.
|
// Text creates a text parser.
|
||||||
func Text(r io.Reader) *TextParser {
|
func Text(r io.Reader) *TextParser {
|
||||||
return &TextParser{
|
return &TextParser{
|
||||||
in: bufio.NewScanner(r),
|
in: bufio.NewScanner(r),
|
||||||
last: new(Record),
|
last: new(record.Record),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the next line.
|
// Parse the next line.
|
||||||
func (p *TextParser) Parse() bool {
|
func (p *TextParser) Parse() bool {
|
||||||
if p.err != nil || !p.in.Scan() {
|
if p.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !p.in.Scan() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
p.err = p.last.FromText(p.in.Bytes())
|
p.err = p.last.FromText(p.in.Bytes())
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value returns the most recent record parsed by a call to Parse.
|
// Value returns the most recent record parsed by a call to Parse.
|
||||||
func (p *TextParser) Value() Record {
|
func (p *TextParser) Value() record.Record {
|
||||||
return *p.last
|
return *p.last
|
||||||
}
|
}
|
||||||
|
|
22
logparser/v6/logly/record/json.go
Normal file
22
logparser/v6/logly/record/json.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// For more tutorials: https://blog.learngoprogramming.com
|
||||||
|
//
|
||||||
|
// Copyright © 2018 Inanc Gumus
|
||||||
|
// Learn Go Programming Course
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
//
|
||||||
|
|
||||||
|
package record
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// UnmarshalJSON to a record.
|
||||||
|
func (r *Record) UnmarshalJSON(data []byte) error {
|
||||||
|
type rjson Record
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, (*rjson)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.validate()
|
||||||
|
}
|
25
logparser/v6/logly/record/record.go
Normal file
25
logparser/v6/logly/record/record.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package record
|
||||||
|
|
||||||
|
const fieldsLength = 4
|
||||||
|
|
||||||
|
// Record stores fields of a log line.
|
||||||
|
type Record struct {
|
||||||
|
Domain string
|
||||||
|
Page string
|
||||||
|
Visits int
|
||||||
|
Uniques int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum the numeric fields with another record.
|
||||||
|
func (r *Record) Sum(other Record) {
|
||||||
|
r.Visits += other.Visits
|
||||||
|
r.Uniques += other.Uniques
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all the fields of this record.
|
||||||
|
func (r *Record) Reset() {
|
||||||
|
r.Domain = ""
|
||||||
|
r.Page = ""
|
||||||
|
r.Visits = 0
|
||||||
|
r.Uniques = 0
|
||||||
|
}
|
36
logparser/v6/logly/record/sum.go
Normal file
36
logparser/v6/logly/record/sum.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// For more tutorials: https://blog.learngoprogramming.com
|
||||||
|
//
|
||||||
|
// Copyright © 2018 Inanc Gumus
|
||||||
|
// Learn Go Programming Course
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
//
|
||||||
|
|
||||||
|
package record
|
||||||
|
|
||||||
|
// Sum groups the records by summing their numeric fields.
|
||||||
|
type Sum struct {
|
||||||
|
sum map[string]Record
|
||||||
|
}
|
||||||
|
|
||||||
|
// SumGroup the records by domain.
|
||||||
|
func SumGroup() *Sum {
|
||||||
|
return &Sum{
|
||||||
|
sum: make(map[string]Record),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group the record.
|
||||||
|
func (s *Sum) Group(r Record) {
|
||||||
|
k := r.Domain
|
||||||
|
r.Sum(s.sum[k])
|
||||||
|
s.sum[k] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Records returns the grouped records.
|
||||||
|
func (s *Sum) Records() []Record {
|
||||||
|
var out []Record
|
||||||
|
for _, res := range s.sum {
|
||||||
|
out = append(out, res)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
36
logparser/v6/logly/record/text.go
Normal file
36
logparser/v6/logly/record/text.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// For more tutorials: https://blog.learngoprogramming.com
|
||||||
|
//
|
||||||
|
// Copyright © 2018 Inanc Gumus
|
||||||
|
// Learn Go Programming Course
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
//
|
||||||
|
|
||||||
|
package record
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FromText unmarshals the log line into this record.
|
||||||
|
func (r *Record) FromText(p []byte) (err error) {
|
||||||
|
fields := strings.Fields(string(p))
|
||||||
|
|
||||||
|
if len(fields) != fieldsLength {
|
||||||
|
return fmt.Errorf("wrong number of fields %q", fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Domain = fields[0]
|
||||||
|
r.Page = fields[1]
|
||||||
|
|
||||||
|
const msg = "record.UnmarshalText %q: %v"
|
||||||
|
if r.Visits, err = strconv.Atoi(fields[2]); err != nil {
|
||||||
|
return fmt.Errorf(msg, "visits", err)
|
||||||
|
}
|
||||||
|
if r.Uniques, err = strconv.Atoi(fields[3]); err != nil {
|
||||||
|
return fmt.Errorf(msg, "uniques", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.validate()
|
||||||
|
}
|
31
logparser/v6/logly/record/validate.go
Normal file
31
logparser/v6/logly/record/validate.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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 record
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// validate whether the current record is valid or not.
|
||||||
|
func (r *Record) validate() error {
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.Domain == "":
|
||||||
|
msg = "record.domain cannot be empty"
|
||||||
|
case r.Page == "":
|
||||||
|
msg = "record.page cannot be empty"
|
||||||
|
case r.Visits < 0:
|
||||||
|
msg = "record.visits cannot be negative"
|
||||||
|
case r.Uniques < 0:
|
||||||
|
msg = "record.uniques cannot be negative"
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg != "" {
|
||||||
|
return errors.New(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
29
logparser/v6/logly/report/json.go
Normal file
29
logparser/v6/logly/report/json.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// For more tutorials: https://blog.learngoprogramming.com
|
||||||
|
//
|
||||||
|
// Copyright © 2018 Inanc Gumus
|
||||||
|
// Learn Go Programming Course
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
//
|
||||||
|
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/inancgumus/learngo/logparser/v6/logly/record"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSON generates a json report.
|
||||||
|
func JSON(w io.Writer, rs []record.Record) error {
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
|
||||||
|
for _, r := range rs {
|
||||||
|
err := enc.Encode(&r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
49
logparser/v6/logly/report/text.go
Normal file
49
logparser/v6/logly/report/text.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// For more tutorials: https://brecord.learngoprogramming.com
|
||||||
|
//
|
||||||
|
// Copyright © 2018 Inanc Gumus
|
||||||
|
// Learn Go Programming Course
|
||||||
|
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||||
|
//
|
||||||
|
|
||||||
|
package report
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/inancgumus/learngo/logparser/v6/logly/record"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Text generates a text report.
|
||||||
|
func Text(w io.Writer, rs []record.Record) error {
|
||||||
|
tw := tabwriter.NewWriter(
|
||||||
|
w,
|
||||||
|
0, // minWidth
|
||||||
|
4, // tabWidth
|
||||||
|
4, // padding
|
||||||
|
' ', // padChar
|
||||||
|
0, // flags
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "DOMAINS\tPAGES\tVISITS\tUNIQUES\n")
|
||||||
|
fmt.Fprintf(tw, "-------\t-----\t------\t-------\n")
|
||||||
|
|
||||||
|
var total record.Record
|
||||||
|
for _, r := range rs {
|
||||||
|
total.Sum(r)
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\t%d\t%d\n",
|
||||||
|
r.Domain, r.Page,
|
||||||
|
r.Visits, r.Uniques,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(tw, "\t\t\t\n")
|
||||||
|
fmt.Fprintf(tw, "%s\t%s\t%d\t%d\n",
|
||||||
|
"TOTAL", "",
|
||||||
|
total.Visits, total.Uniques,
|
||||||
|
)
|
||||||
|
|
||||||
|
return tw.Flush()
|
||||||
|
}
|
@ -8,49 +8,28 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/inancgumus/learngo/logparser/v6/parse"
|
"github.com/inancgumus/learngo/logparser/v6/logly/parse"
|
||||||
"github.com/inancgumus/learngo/logparser/v6/report"
|
"github.com/inancgumus/learngo/logparser/v6/logly/record"
|
||||||
|
"github.com/inancgumus/learngo/logparser/v6/logly/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// trace.Start(os.Stderr)
|
var (
|
||||||
// defer trace.Stop()
|
p = parse.CountRecords(parse.Text(os.Stdin))
|
||||||
|
g = record.SumGroup()
|
||||||
|
)
|
||||||
|
|
||||||
var p parse.Parser
|
|
||||||
// p = parse.Text(os.Stdin)
|
|
||||||
p = parse.JSON(os.Stdin)
|
|
||||||
p = parse.CountRecords(p)
|
|
||||||
|
|
||||||
r := report.Text(os.Stdout)
|
|
||||||
|
|
||||||
var out []parse.Record
|
|
||||||
for p.Parse() {
|
for p.Parse() {
|
||||||
r := p.Value()
|
g.Group(p.Value())
|
||||||
|
|
||||||
// if !parse.Filter(r) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
// sum.group(r)
|
|
||||||
out = append(out, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.Err(); err != nil {
|
if err := p.Err(); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// var out []parse.Record
|
if err := report.Text(os.Stdout, g.Records()); err != nil {
|
||||||
// for sum.More() {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
if err := r.Generate(out); err != nil {
|
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(p.(*parse.Count).Last(), "records are processed.")
|
|
||||||
}
|
}
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
package parse
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const fieldsLength = 4
|
|
||||||
|
|
||||||
// Record stores fields of a log line.
|
|
||||||
type Record struct {
|
|
||||||
Domain string
|
|
||||||
Page string
|
|
||||||
Visits int
|
|
||||||
Uniques int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum the numeric fields with another record.
|
|
||||||
func (r *Record) Sum(other Record) {
|
|
||||||
r.Visits += other.Visits
|
|
||||||
r.Uniques += other.Uniques
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset all the fields of this record.
|
|
||||||
func (r *Record) Reset() {
|
|
||||||
r.Domain = ""
|
|
||||||
r.Page = ""
|
|
||||||
r.Visits = 0
|
|
||||||
r.Uniques = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromText unmarshals the log line into this record.
|
|
||||||
func (r *Record) FromText(p []byte) (err error) {
|
|
||||||
fields := strings.Fields(string(p))
|
|
||||||
|
|
||||||
if len(fields) != fieldsLength {
|
|
||||||
return fmt.Errorf("wrong number of fields %q", fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Domain = fields[0]
|
|
||||||
r.Page = fields[1]
|
|
||||||
|
|
||||||
const msg = "record.UnmarshalText %q: %v"
|
|
||||||
if r.Visits, err = strconv.Atoi(fields[2]); err != nil {
|
|
||||||
return fmt.Errorf(msg, "visits", err)
|
|
||||||
}
|
|
||||||
if r.Uniques, err = strconv.Atoi(fields[3]); err != nil {
|
|
||||||
return fmt.Errorf(msg, "uniques", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON to a record.
|
|
||||||
func (r *Record) UnmarshalJSON(data []byte) error {
|
|
||||||
type rjson Record
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, (*rjson)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate whether the current record is valid or not.
|
|
||||||
func (r *Record) validate() error {
|
|
||||||
var msg string
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case r.Domain == "":
|
|
||||||
msg = "record.domain cannot be empty"
|
|
||||||
case r.Page == "":
|
|
||||||
msg = "record.page cannot be empty"
|
|
||||||
case r.Visits < 0:
|
|
||||||
msg = "record.visits cannot be negative"
|
|
||||||
case r.Uniques < 0:
|
|
||||||
msg = "record.uniques cannot be negative"
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != "" {
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -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 report
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/inancgumus/learngo/logparser/v6/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSONReport generates a JSON report.
|
|
||||||
type JSONReport struct {
|
|
||||||
w *json.Encoder
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON returns a JSON report generator.
|
|
||||||
func JSON(w io.Writer) *JSONReport {
|
|
||||||
return &JSONReport{
|
|
||||||
w: json.NewEncoder(w),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the report from the records.
|
|
||||||
func (jr *JSONReport) Generate(rs []parse.Record) error {
|
|
||||||
for _, r := range rs {
|
|
||||||
if err := jr.w.Encode(&r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -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"
|
|
||||||
"io"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/inancgumus/learngo/logparser/v6/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TextReport report generator.
|
|
||||||
type TextReport struct {
|
|
||||||
w *tabwriter.Writer
|
|
||||||
total parse.Record
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text creates a report generator.
|
|
||||||
func Text(w io.Writer) *TextReport {
|
|
||||||
tw := tabwriter.NewWriter(w,
|
|
||||||
0, // minWidth
|
|
||||||
4, // tabWidth
|
|
||||||
4, // padding
|
|
||||||
' ', // padChar
|
|
||||||
0, // flags
|
|
||||||
)
|
|
||||||
|
|
||||||
return &TextReport{w: tw}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the report from the records.
|
|
||||||
// tabwriter caches, the memory usage will be high
|
|
||||||
// if you send a large number of records to Generate.
|
|
||||||
func (tr *TextReport) Generate(rs []parse.Record) error {
|
|
||||||
fmt.Fprintf(tr.w, "DOMAINS\tPAGES\tVISITS\tUNIQUES\n")
|
|
||||||
fmt.Fprintf(tr.w, "-------\t-----\t------\t-------\n")
|
|
||||||
|
|
||||||
for _, r := range rs {
|
|
||||||
tr.total.Sum(r)
|
|
||||||
|
|
||||||
fmt.Fprintf(tr.w, "%s\t%s\t%d\t%d\n",
|
|
||||||
r.Domain, r.Page,
|
|
||||||
r.Visits, r.Uniques,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(tr.w, "\t\t\t\n")
|
|
||||||
fmt.Fprintf(tr.w, "%s\t%s\t%d\t%d\n",
|
|
||||||
"TOTAL", "",
|
|
||||||
tr.total.Visits, tr.total.Uniques,
|
|
||||||
)
|
|
||||||
|
|
||||||
return tr.w.Flush()
|
|
||||||
}
|
|
Reference in New Issue
Block a user