penguin/golifehk

@golifehk bot in Telegram

commit 89770c0864fc1644c17df3573049bc44adc85881

author斟酌 鵬兄 <tgckpg@gmail.com>
date2022-09-15T12:00:49Z
subjectSupport route listing & search
commit 89770c0864fc1644c17df3573049bc44adc85881
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2022-09-15T12:00:49Z

    Support route listing & search
---
 datasources/mtr/bus/QueryResult.go |  84 ++++++++++++++++------
 datasources/mtr/bus/query.go       | 143 ++++++++++++++++++++++++++++---------
 datasources/mtr/bus/query_test.go  |   2 +-
 3 files changed, 173 insertions(+), 56 deletions(-)

diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go
index fd45f6f..a6286d5 100644
--- a/datasources/mtr/bus/QueryResult.go
+++ b/datasources/mtr/bus/QueryResult.go
@@ -2,6 +2,7 @@ package bus
 
 import (
     "fmt"
+    "sort"
     "strings"
 )
 
@@ -9,8 +10,33 @@ type QueryResult struct {
     Schedules *map[BusStop] *BusStopBuses
     Lang string
     Error error
+    Query *QueryObject
 }
 
+func writeShortRoute( lang *string, sb *strings.Builder, b *BusStop ) {
+    if b.PrevStop() != nil {
+        sb.WriteString( (*b.PrevStop().Name)[ *lang ] )
+        sb.WriteString( " > " )
+    }
+
+    sb.WriteString( "**" )
+    sb.WriteString( (*b.Name)[ *lang ] )
+    sb.WriteString( "**" )
+
+    if b.NextStop() != nil {
+        sb.WriteString( " > " )
+        sb.WriteString( (*b.NextStop().Name)[ *lang ] )
+    }
+
+    sb.WriteString( "\n" )
+}
+
+type ByRouteId []BusStop
+
+func (a ByRouteId) Len() int           { return len(a) }
+func (a ByRouteId) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a ByRouteId) Less(i, j int) bool { return a[i].RouteId < a[j].RouteId }
+
 func ( result QueryResult ) Message() string {
 
     if result.Error != nil {
@@ -19,33 +45,51 @@ func ( result QueryResult ) Message() string {
 
     sb := strings.Builder{}
 
-    for busStop, buses := range *result.Schedules {
+    if result.Schedules == nil {
 
-        if busStop.PrevStop() != nil {
-            sb.WriteString( (*busStop.PrevStop().Name)[ result.Lang ] )
-            sb.WriteString( " > *" )
-        }
+        query := *result.Query
+        if query.Route == "" {
+            sort.Sort( ByRouteId( *query.BusStops ) )
+            for _, busStop := range *query.BusStops {
+                sb.WriteString( busStop.RouteId )
+                sb.WriteString( " " )
+                writeShortRoute( &result.Lang, &sb, &busStop )
+            }
+        } else if query.BusStops == nil && query.RouteStarts != nil {
+            for d, b := range *query.RouteStarts {
+                sb.WriteString( query.Route )
+                sb.WriteString( "-" )
+                sb.WriteString( d )
+                sb.WriteString( "\n " )
 
-        sb.WriteString( (*busStop.Name)[ result.Lang ] )
-        sb.WriteString( "*" )
+                for {
+                    sb.WriteString( (*b.Name)[ result.Lang ] )
+                    b = b.NextStop()
+                    if b == nil {
+                        break
+                    }
 
-        if busStop.NextStop() != nil {
-            sb.WriteString( " > " )
-            sb.WriteString( (*busStop.NextStop().Name)[ result.Lang ] )
+                    sb.WriteString( " > " )
+                }
+                sb.WriteString( "\n" )
+            }
+        } else {
+            sb.WriteString( "Unreachable condition occured!?" )
         }
-
-        sb.WriteString( "\n" )
-        for _, bus := range buses.Buses {
-            sb.WriteString( "  * " )
-            if bus.ETAText == "" {
-                sb.WriteString( bus.ETDText )
-            } else {
-                sb.WriteString( bus.ETAText )
+    } else {
+        for busStop, buses := range *result.Schedules {
+            writeShortRoute( &result.Lang, &sb, &busStop )
+            for _, bus := range buses.Buses {
+                sb.WriteString( "  * " )
+                if bus.ETAText == "" {
+                    sb.WriteString( bus.ETDText )
+                } else {
+                    sb.WriteString( bus.ETAText )
+                }
+                sb.WriteString( "\n" )
             }
             sb.WriteString( "\n" )
         }
-
-        sb.WriteString( "\n" )
     }
 
     return sb.String()
diff --git a/datasources/mtr/bus/query.go b/datasources/mtr/bus/query.go
index a3a7a8b..ccb4941 100644
--- a/datasources/mtr/bus/query.go
+++ b/datasources/mtr/bus/query.go
@@ -7,37 +7,66 @@ import (
     "github.com/tgckpg/golifehk/utils"
 )
 
-type queryObj struct {
+type qTerm struct {
+    Org string
+    Value string
+    ProblyRoute bool
+}
+
+type QueryObject struct {
     Route string
     BusStops *[]BusStop
-    Search string
+    RouteStarts *map[string] *BusStop
+    SearchTerms *[] *qTerm
 }
 
-type qTerm struct {
-    Value string
-    ProblyRoute bool
+func NotFound ( terms *[] *qTerm ) string {
+    sb := strings.Builder{}
+    sb.WriteString( "Not Found" )
+    if 0 < len( *terms ) {
+        sb.WriteString( ":" )
+        for _, term := range *terms {
+            sb.WriteString( " \"" )
+            sb.WriteString( term.Org )
+            sb.WriteString( "\"" )
+        }
+    }
+
+    return sb.String()
 }
 
 func Query( lang string, message string ) *QueryResult {
 
+    qr := QueryResult{ Lang: lang }
+
     qo, err := parse( message )
     if err != nil {
-        return &QueryResult{ Error: err }
+        qr.Error = err
+        return &qr
     }
 
+    qr.Query = qo
+
     if qo.Route != "" {
 
+        if qo.BusStops == nil {
+            return &qr
+        }
+
         if len( *qo.BusStops ) == 0 {
-            return &QueryResult{ Error: errors.New( "Result not found" ) }
+            qr.Error = errors.New( NotFound( qo.SearchTerms ) )
+            return &qr
         }
 
         schedules, err := getSchedule( lang, qo.Route )
         if err != nil {
-            return &QueryResult{ Error: err }
+            qr.Error = err
+            return &qr
         }
 
         if len( schedules.BusStops ) == 0 {
-            return &QueryResult{ Error: errors.New( "Schedules are empty. Maybe outside service time?" ) }
+            qr.Error = errors.New( "Schedules are empty...perhaps Out of Service Time?" )
+            return &qr
         }
 
         matches := map[BusStop] *BusStopBuses{}
@@ -52,10 +81,16 @@ func Query( lang string, message string ) *QueryResult {
 
         }
 
-        return &QueryResult{ Lang: lang, Schedules: &matches }
+        qr.Schedules = &matches
+        return &qr
+    } else {
+        if len( *qo.BusStops ) == 0 {
+            return &QueryResult{ Error: errors.New( NotFound( qo.SearchTerms ) ) }
+        }
+
+        return &qr
     }
 
-    return &QueryResult{ Error: errors.New( "No Result" ) }
 }
 
 func test( entry BusStop, val string ) bool {
@@ -68,7 +103,7 @@ func test( entry BusStop, val string ) bool {
     return false
 }
 
-func parse( line string ) ( *queryObj, error ) {
+func parse( line string ) ( *QueryObject, error ) {
     busStops, err := getBusStops()
 
     if err != nil {
@@ -76,18 +111,22 @@ func parse( line string ) ( *queryObj, error ) {
     }
 
     var route string = ""
-    var searches = []string{}
     matches := []BusStop{}
 
     // Sanitize and assume properties for each of the keywords
-    terms := []qTerm{}
+    terms := [] *qTerm{}
     for _, val := range strings.Split( line, " " ) {
-        val = strings.ToUpper( strings.Trim( val, " " ) )
+
+        if val == "" {
+            continue
+        }
+
         term := qTerm{
-            Value: val,
+            Org: val,
+            Value: strings.ToUpper( strings.Trim( val, " " ) ),
             ProblyRoute: strings.ContainsAny( val, utils.ROUTE_CHARS ),
         }
-        terms = append( terms, term )
+        terms = append( terms, &term )
     }
 
     // Search for route name first, otherwise search in other props
@@ -105,7 +144,6 @@ func parse( line string ) ( *queryObj, error ) {
                 break 
             }
 
-            searches = append( searches, term.Value )
             if test( entry, term.Value ) {
                 matches = append( matches, entry )
                 break
@@ -113,27 +151,62 @@ func parse( line string ) ( *queryObj, error ) {
         }
     }
 
-    // If route found, filter out all other route
-    // then search within that route
-    if route != "" {
-        if 0 < len( searches ) {
-            matches_in := []BusStop{}
-            for _, entry := range matches {
-                if entry.RouteId != route {
-                    continue
-                }
+    searchTerms := [] *qTerm{}
+    matches_in := []BusStop{}
+
+    for _, term := range terms {
+        if term.ProblyRoute {
+            continue
+        }
+        searchTerms = append( searchTerms, term )
+    }
 
-                for _, val := range searches {
-                    if test( entry, val ) {
-                        matches_in = append( matches_in, entry )
-                        break
-                    }
+    if 0 < len( searchTerms ) {
+        // If route found, filter out all other route
+        // then search the terms within that route
+        for _, entry := range matches {
+            if route != "" && entry.RouteId != route {
+                continue
+            }
+
+            for _, term := range searchTerms {
+                if test( entry, term.Value ) {
+                    matches_in = append( matches_in, entry )
+                    break
                 }
             }
-            matches = matches_in
-        } else {
         }
+
+        matches = matches_in
+
+        return &QueryObject{
+            Route: route,
+            BusStops: &matches,
+            SearchTerms: &searchTerms,
+        }, err
+
+    } else if route != "" {
+        // Route listing
+        st := map[string] *BusStop{}
+
+        for _, entry := range *busStops {
+            if entry.RouteId != route {
+                continue
+            }
+
+            if _, ok := st[ entry.Direction ]; ok {
+                continue
+            }
+
+            st[ entry.Direction ] = &entry
+
+            for st[ entry.Direction ].PrevStop() != nil {
+                st[ entry.Direction ] = st[ entry.Direction ].PrevStop()
+            }
+        }
+
+        return &QueryObject{ Route: route, RouteStarts: &st, BusStops: nil }, nil
     }
 
-    return &queryObj{ Route: route, BusStops: &matches }, err
+    return nil, fmt.Errorf( "Cannot parse: %s", line )
 }
diff --git a/datasources/mtr/bus/query_test.go b/datasources/mtr/bus/query_test.go
index cbea0d1..b1decb2 100644
--- a/datasources/mtr/bus/query_test.go
+++ b/datasources/mtr/bus/query_test.go
@@ -6,7 +6,7 @@ import (
 )
 
 func TestQuerySchedule( t *testing.T ) {
-    qo := Query( "zh", "K73 池" )
+    qo := Query( "zh", "水" )
 
     fmt.Print( qo.Message() )
 }