mirror of https://github.com/deuill/farsight.git
146 lines
3.5 KiB
Go
146 lines
3.5 KiB
Go
// Copyright 2016 Alexander Palaistras. All rights reserved.
|
|
// Use of this source code is governed by the MIT license that can be found in
|
|
// the LICENSE file.
|
|
|
|
package farsight
|
|
|
|
import (
|
|
// Standard library.
|
|
"fmt"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
// Internal packages.
|
|
"github.com/deuill/farsight/parser"
|
|
"github.com/deuill/farsight/source"
|
|
|
|
// Pre-defined sources and parsers.
|
|
_ "github.com/deuill/farsight/parser/html"
|
|
_ "github.com/deuill/farsight/source/http"
|
|
)
|
|
|
|
var (
|
|
regexpValidInt = `[-+]?[0-9]+`
|
|
regexpValidFloat = `[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?`
|
|
)
|
|
|
|
// Fetch data from source pointed to by URI in `src`, and store to arbitrary
|
|
// struct pointed to by `dest`. Data is parsed according to `kind`, and has to
|
|
// correspond to a registered parser.
|
|
func Fetch(src string, dest interface{}, kind string) error {
|
|
// Verify destination value type.
|
|
val := reflect.ValueOf(dest)
|
|
|
|
if val.Kind() != reflect.Ptr && val.Elem().Kind() != reflect.Struct {
|
|
return fmt.Errorf("Invalid destination type '%s', expected 'ptr'", val.Kind().String())
|
|
}
|
|
|
|
// Fetch data from source defined in `src`.
|
|
buf, err := source.Fetch(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Parse raw data and return parsed document.
|
|
doc, err := parser.Parse(kind, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Populate destination fields from parsed document.
|
|
if err = populateStruct(doc, val.Elem()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Set struct fields from document, filtered by tags marked by "farsight"
|
|
// definitions.
|
|
func populateStruct(doc parser.Document, dest reflect.Value) error {
|
|
// Set each struct field in sequence.
|
|
for i := 0; i < dest.NumField(); i++ {
|
|
f := dest.Field(i)
|
|
ft := dest.Type().Field(i)
|
|
|
|
// Skip field if `farsight` tag is unset or explicitly ignored.
|
|
attr := ft.Tag.Get("farsight")
|
|
if attr == "" || attr == "-" {
|
|
continue
|
|
}
|
|
|
|
// Filter document by tag and set field.
|
|
subdoc, err := doc.Filter(attr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = setField(subdoc, f); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Set struct field for concrete value contained within `doc`.
|
|
func setField(doc parser.Document, field reflect.Value) error {
|
|
// Get string value from document.
|
|
val := doc.String()
|
|
|
|
// Determine field type and set value, converting if necessary.
|
|
switch field.Kind() {
|
|
case reflect.String:
|
|
field.SetString(val)
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if val == "" {
|
|
field.SetInt(0)
|
|
} else {
|
|
// Truncate string to valid integer.
|
|
val = regexp.MustCompile(regexpValidInt).FindString(val)
|
|
|
|
// Parse string and return integer value.
|
|
num, err := strconv.ParseInt(val, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
field.SetInt(num)
|
|
}
|
|
case reflect.Float32, reflect.Float64:
|
|
if val == "" {
|
|
field.SetFloat(0)
|
|
} else {
|
|
// Truncate string to valid floating point number.
|
|
val = regexp.MustCompile(regexpValidFloat).FindString(val)
|
|
|
|
// Parse string and return floating point value.
|
|
num, err := strconv.ParseFloat(val, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
field.SetFloat(num)
|
|
}
|
|
case reflect.Slice:
|
|
// Decompose document into list and prepare destination slice.
|
|
docs := doc.Slice()
|
|
slice := reflect.MakeSlice(field.Type(), len(docs), cap(docs))
|
|
|
|
for i, d := range docs {
|
|
if err := setField(d, slice.Index(i)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
field.Set(slice)
|
|
case reflect.Struct:
|
|
return populateStruct(doc, field)
|
|
default:
|
|
return fmt.Errorf("Unable to set unknown field type '%s'", field.Kind().String())
|
|
}
|
|
|
|
return nil
|
|
}
|