Refactoring codes for more tg message types
This commit is contained in:
59
query/GeoLocation.go
Normal file
59
query/GeoLocation.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/tidwall/geodesic"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type GeoLocation struct {
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
}
|
||||
|
||||
func (b GeoLocation) HasGeoLocation() bool { return true }
|
||||
|
||||
func (b GeoLocation) Lat() float64 { return b.Latitude }
|
||||
func (b GeoLocation) Lon() float64 { return b.Longitude }
|
||||
|
||||
func (b GeoLocation) Dist(lat float64, lon float64) float64 {
|
||||
var dist float64
|
||||
geodesic.WGS84.Inverse(
|
||||
lat, lon,
|
||||
b.Latitude, b.Longitude,
|
||||
&dist, nil, nil,
|
||||
)
|
||||
return dist
|
||||
}
|
||||
|
||||
type NoGeoLocation struct{}
|
||||
|
||||
func (b NoGeoLocation) HasGeoLocation() bool { return false }
|
||||
func (b NoGeoLocation) Lat() float64 { return 0 }
|
||||
func (b NoGeoLocation) Lon() float64 { return 0 }
|
||||
func (b NoGeoLocation) Dist(lat, lon float64) float64 { return 0 }
|
||||
func (b NoGeoLocation) Register(map[string]struct{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoLocations []ISearchable
|
||||
|
||||
func (m GeoLocations) SortByNearest(p GeoLocation) {
|
||||
sort.Slice(m, func(i, j int) bool {
|
||||
return m[i].Dist(p.Lat(), p.Lon()) < m[j].Dist(p.Lat(), p.Lon())
|
||||
})
|
||||
}
|
||||
|
||||
func (b GeoLocation) Register(map[string]struct{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m GeoLocations) Clean() *GeoLocations {
|
||||
registers := map[string]struct{}{}
|
||||
items := &GeoLocations{}
|
||||
for _, item := range m {
|
||||
if item.HasGeoLocation() && item.Register(registers) {
|
||||
*items = append(*items, item)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package query
|
||||
|
||||
type IQueryResult interface {
|
||||
Message() ( string, error )
|
||||
}
|
||||
@@ -1,40 +1,15 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ISearchable interface {
|
||||
Test( string ) bool
|
||||
GetKey() *string
|
||||
HasWords() bool
|
||||
Test(string) bool
|
||||
GetKey() *string
|
||||
|
||||
HasGeoLocation() bool
|
||||
Lat() float64
|
||||
Lon() float64
|
||||
Dist(lat float64, lon float64) float64
|
||||
|
||||
// Clean() filtering
|
||||
Register(map[string]struct{}) bool
|
||||
}
|
||||
|
||||
type Searchable struct {
|
||||
Key *string
|
||||
SearchData *[] *string
|
||||
}
|
||||
|
||||
func ( this *Searchable ) Test( val string ) bool {
|
||||
|
||||
data := this.SearchData
|
||||
if data == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range *data {
|
||||
if strings.Contains( *v, val ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ( this *Searchable ) GetKey() *string {
|
||||
return this.Key
|
||||
}
|
||||
|
||||
type ByKey [] ISearchable
|
||||
|
||||
func (a ByKey) Len() int { return len(a) }
|
||||
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByKey) Less(i, j int) bool { return *a[i].GetKey() < *a[j].GetKey() }
|
||||
|
||||
43
query/Words.go
Normal file
43
query/Words.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Words struct {
|
||||
Key *string
|
||||
SearchData *[]*string
|
||||
}
|
||||
|
||||
func (this *Words) HasWords() bool { return true }
|
||||
|
||||
func (this *Words) Test(val string) bool {
|
||||
|
||||
data := this.SearchData
|
||||
if data == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range *data {
|
||||
if strings.Contains(*v, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Words) GetKey() *string {
|
||||
return this.Key
|
||||
}
|
||||
|
||||
type ByKey []ISearchable
|
||||
|
||||
func (a ByKey) Len() int { return len(a) }
|
||||
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByKey) Less(i, j int) bool { return *a[i].GetKey() < *a[j].GetKey() }
|
||||
|
||||
type NoWords struct{}
|
||||
|
||||
func (this NoWords) HasWords() bool { return false }
|
||||
func (this NoWords) Test(val string) bool { return false }
|
||||
func (this NoWords) GetKey() *string { return nil }
|
||||
33
query/geo_test.go
Normal file
33
query/geo_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type City struct {
|
||||
Name string
|
||||
NoWords
|
||||
GeoLocation
|
||||
}
|
||||
|
||||
func TestGeo(t *testing.T) {
|
||||
|
||||
B := GeoLocations{
|
||||
City{Name: "Osaka", GeoLocation: GeoLocation{34.6937, 135.5023}},
|
||||
City{Name: "Nagoya", GeoLocation: GeoLocation{35.1815, 136.9066}},
|
||||
City{Name: "Sapporo", GeoLocation: GeoLocation{43.0618, 141.3545}},
|
||||
City{Name: "Yokohama", GeoLocation: GeoLocation{35.4437, 139.6380}},
|
||||
}
|
||||
|
||||
// Reference point A (Tokyo)
|
||||
A := GeoLocation{35.6895, 139.6917}
|
||||
|
||||
// Sort by distance
|
||||
B.SortByNearest(A)
|
||||
|
||||
// Print result
|
||||
for _, loc := range B {
|
||||
fmt.Printf("%s: %.2f km\n", loc.(City).Name, loc.Dist(A.Lat(), A.Lon())/1000)
|
||||
}
|
||||
}
|
||||
69
query/match_keys.go
Normal file
69
query/match_keys.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MatchKeys(line string, entries *[]ISearchable) (*QueryObject, error) {
|
||||
|
||||
var Key string = ""
|
||||
|
||||
// Sanitize and assume properties for each of the keywords
|
||||
terms := []*QTerm{}
|
||||
for _, val := range strings.Split(line, " ") {
|
||||
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
term := QTerm{
|
||||
Org: val,
|
||||
Value: strings.Trim(val, " "),
|
||||
}
|
||||
terms = append(terms, &term)
|
||||
}
|
||||
|
||||
lookForKey:
|
||||
for _, term := range terms {
|
||||
for _, entry := range *entries {
|
||||
if term.Value == *entry.GetKey() {
|
||||
Key = term.Value
|
||||
term.IsKey = true
|
||||
break lookForKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches := []ISearchable{}
|
||||
|
||||
if Key != "" && len(terms) == 1 {
|
||||
for _, entry := range *entries {
|
||||
if Key == *entry.GetKey() {
|
||||
matches = append(matches, entry)
|
||||
}
|
||||
}
|
||||
return &QueryObject{Key: Key, Results: &matches, SearchTerms: &terms}, nil
|
||||
} else if 0 < len(terms) {
|
||||
for _, entry := range *entries {
|
||||
anyFailed := false
|
||||
for _, term := range terms {
|
||||
if term.IsKey {
|
||||
continue
|
||||
}
|
||||
|
||||
if !((Key == "" || Key == *entry.GetKey()) && entry.Test(term.Value)) {
|
||||
anyFailed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !anyFailed {
|
||||
matches = append(matches, entry)
|
||||
}
|
||||
}
|
||||
return &QueryObject{Key: Key, Results: &matches, SearchTerms: &terms}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Cannot parse: %s", line)
|
||||
}
|
||||
29
query/match_locations.go
Normal file
29
query/match_locations.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func MatchNearest(p GeoLocation, entries *[]ISearchable, dist float64, limit int) (*QueryObject, error) {
|
||||
|
||||
terms := []*QTerm{
|
||||
{
|
||||
Org: fmt.Sprintf("%f, %f", p.Lat(), p.Lon()),
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
|
||||
var locs *GeoLocations
|
||||
|
||||
locs = GeoLocations(*entries).Clean()
|
||||
locs.SortByNearest(p)
|
||||
|
||||
matches := []ISearchable{}
|
||||
for i, loc := range *locs {
|
||||
if i < limit {
|
||||
matches = append(matches, loc)
|
||||
}
|
||||
}
|
||||
|
||||
return &QueryObject{Key: "", Results: &matches, SearchTerms: &terms}, nil
|
||||
}
|
||||
27
query/objects.go
Normal file
27
query/objects.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package query
|
||||
|
||||
type QTerm struct {
|
||||
Org string
|
||||
Value string
|
||||
IsKey bool
|
||||
}
|
||||
|
||||
type QueryMessage struct {
|
||||
Lang string
|
||||
Text string
|
||||
Location *GeoLocation
|
||||
}
|
||||
|
||||
type QueryObject struct {
|
||||
Key string
|
||||
Message *QueryMessage
|
||||
SearchTerms *[]*QTerm
|
||||
Results *[]ISearchable
|
||||
}
|
||||
|
||||
type IQueryResult interface {
|
||||
Message() (string, error)
|
||||
DataType() string
|
||||
GetTableData() [][]map[string]string
|
||||
Consumed() bool
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type QTerm struct {
|
||||
Org string
|
||||
Value string
|
||||
IsKey bool
|
||||
}
|
||||
|
||||
type QueryObject struct {
|
||||
Key string
|
||||
SearchTerms *[] *QTerm
|
||||
Results *[] ISearchable
|
||||
}
|
||||
|
||||
func Parse( line string, entries *[] ISearchable ) ( *QueryObject, error ) {
|
||||
|
||||
var Key string = ""
|
||||
|
||||
// Sanitize and assume properties for each of the keywords
|
||||
terms := [] *QTerm{}
|
||||
for _, val := range strings.Split( line, " " ) {
|
||||
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
term := QTerm{
|
||||
Org: val,
|
||||
Value: strings.Trim( val, " " ),
|
||||
}
|
||||
terms = append( terms, &term )
|
||||
}
|
||||
|
||||
lookForKey:
|
||||
for _, term := range terms {
|
||||
for _, entry := range *entries {
|
||||
if term.Value == *entry.GetKey() {
|
||||
Key = term.Value
|
||||
term.IsKey = true
|
||||
break lookForKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches := [] ISearchable{}
|
||||
|
||||
if Key != "" && len( terms ) == 1 {
|
||||
for _, entry := range *entries {
|
||||
if Key == *entry.GetKey() {
|
||||
matches = append( matches, entry )
|
||||
}
|
||||
}
|
||||
return &QueryObject{ Key: Key, Results: &matches, SearchTerms: &terms }, nil
|
||||
} else if 0 < len( terms ) {
|
||||
for _, entry := range *entries {
|
||||
anyFailed := false
|
||||
for _, term := range terms {
|
||||
if term.IsKey {
|
||||
continue
|
||||
}
|
||||
|
||||
if !( ( Key == "" || Key == *entry.GetKey() ) && entry.Test( term.Value ) ) {
|
||||
anyFailed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !anyFailed {
|
||||
matches = append( matches, entry )
|
||||
}
|
||||
}
|
||||
return &QueryObject{ Key: Key, Results: &matches, SearchTerms: &terms }, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf( "Cannot parse: %s", line )
|
||||
}
|
||||
@@ -5,75 +5,76 @@ import (
|
||||
)
|
||||
|
||||
type TestItem struct {
|
||||
Searchable
|
||||
Words
|
||||
NoGeoLocation
|
||||
}
|
||||
|
||||
func TestQuery( t *testing.T ) {
|
||||
testItems := [] ISearchable{}
|
||||
func TestQuery(t *testing.T) {
|
||||
testItems := []ISearchable{}
|
||||
|
||||
s1234 := "1234"
|
||||
sApple := "Apple"
|
||||
s0000 := "0000"
|
||||
s1234 := "1234"
|
||||
sApple := "Apple"
|
||||
s0000 := "0000"
|
||||
|
||||
sDat0 := "Dat0"
|
||||
data0 := [] *string{}
|
||||
data0 = append( data0, &s1234 )
|
||||
data0 = append( data0, &sApple )
|
||||
t0 := TestItem{}
|
||||
t0.Key = &sDat0
|
||||
t0.SearchData = &data0
|
||||
testItems = append( testItems, &t0 )
|
||||
sDat0 := "Dat0"
|
||||
data0 := []*string{}
|
||||
data0 = append(data0, &s1234)
|
||||
data0 = append(data0, &sApple)
|
||||
t0 := TestItem{}
|
||||
t0.Key = &sDat0
|
||||
t0.SearchData = &data0
|
||||
testItems = append(testItems, &t0)
|
||||
|
||||
sDat1 := "Dat1"
|
||||
data1 := [] *string{}
|
||||
data1 = append( data1, &s0000 )
|
||||
data1 = append( data1, &sApple )
|
||||
t1 := TestItem{}
|
||||
t1.Key = &sDat1
|
||||
t1.SearchData = &data1
|
||||
testItems = append( testItems, &t1 )
|
||||
sDat1 := "Dat1"
|
||||
data1 := []*string{}
|
||||
data1 = append(data1, &s0000)
|
||||
data1 = append(data1, &sApple)
|
||||
t1 := TestItem{}
|
||||
t1.Key = &sDat1
|
||||
t1.SearchData = &data1
|
||||
testItems = append(testItems, &t1)
|
||||
|
||||
sDat2 := "Dat2"
|
||||
data2 := [] *string{}
|
||||
data2 = append( data2, &sDat0 )
|
||||
t2 := TestItem{}
|
||||
t2.Key = &sDat2
|
||||
t2.SearchData = &data2
|
||||
testItems = append( testItems, &t2 )
|
||||
sDat2 := "Dat2"
|
||||
data2 := []*string{}
|
||||
data2 = append(data2, &sDat0)
|
||||
t2 := TestItem{}
|
||||
t2.Key = &sDat2
|
||||
t2.SearchData = &data2
|
||||
testItems = append(testItems, &t2)
|
||||
|
||||
qo, err := Parse( "Apple", &testItems )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
qo, err := MatchKeys("Apple", &testItems)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(*qo.Results) != 2 {
|
||||
t.Error( "Expected 2 results when searching for \"Apple\"" )
|
||||
}
|
||||
if len(*qo.Results) != 2 {
|
||||
t.Error("Expected 2 results when searching for \"Apple\"")
|
||||
}
|
||||
|
||||
qo, err = Parse( "0000", &testItems )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
qo, err = MatchKeys("0000", &testItems)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(*qo.Results) != 1 {
|
||||
t.Error( "Expected 1 results when searching for \"0000\"" )
|
||||
}
|
||||
if len(*qo.Results) != 1 {
|
||||
t.Error("Expected 1 results when searching for \"0000\"")
|
||||
}
|
||||
|
||||
qo, err = Parse( "Dat0", &testItems )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
qo, err = MatchKeys("Dat0", &testItems)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(*qo.Results) != 1 {
|
||||
t.Error( "Expected 1 results when searching for \"Dat0\"" )
|
||||
}
|
||||
if len(*qo.Results) != 1 {
|
||||
t.Error("Expected 1 results when searching for \"Dat0\"")
|
||||
}
|
||||
|
||||
qo, err = Parse( "Dat2 Dat0", &testItems )
|
||||
if err != nil {
|
||||
t.Error( err )
|
||||
}
|
||||
qo, err = MatchKeys("Dat2 Dat0", &testItems)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(*qo.Results) != 1 {
|
||||
t.Error( "Expected 1 results when searching for \"Dat2 Dat0\"" )
|
||||
}
|
||||
if len(*qo.Results) != 1 {
|
||||
t.Error("Expected 1 results when searching for \"Dat2 Dat0\"")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user