diff --git a/datasources/mtr/bus/BusStop.go b/datasources/mtr/bus/BusStop.go index 8e6ea31..c11fb52 100644 --- a/datasources/mtr/bus/BusStop.go +++ b/datasources/mtr/bus/BusStop.go @@ -18,6 +18,9 @@ type BusStop struct { // RouteStops[ StationSeq ] = BusStop RouteStops *map[int]*BusStop + // AltRoutes + AltRoutes *map[string]*BusStop + i18n.Generics } diff --git a/datasources/mtr/bus/QueryResult.go b/datasources/mtr/bus/QueryResult.go index ebe9859..9371715 100644 --- a/datasources/mtr/bus/QueryResult.go +++ b/datasources/mtr/bus/QueryResult.go @@ -1,148 +1,206 @@ package bus import ( - "fmt" - "sort" - "strings" + "fmt" + "sort" + "strconv" + "strings" - query "github.com/tgckpg/golifehk/query" - utils "github.com/tgckpg/golifehk/utils" + query "github.com/tgckpg/golifehk/query" + utils "github.com/tgckpg/golifehk/utils" ) type QueryResult struct { - Schedules *map[*BusStop] *BusStopBuses - Lang string - Error error - Query *query.QueryObject + Schedules *map[*BusStop]*BusStopBuses + Lang string + Error error + Query *query.QueryObject } -func writeShortRoute( lang *string, sb *strings.Builder, b *BusStop ) { - if b.PrevStop() != nil { - utils.WriteMDv2Text( sb, (*b.PrevStop().Name)[ *lang ] ) - sb.WriteString( " \\> " ) - } +func writeShortRoute(lang *string, sb *strings.Builder, b *BusStop) { + if b.PrevStop() != nil { + utils.WriteMDv2Text(sb, (*b.PrevStop().Name)[*lang]) + sb.WriteString(" \\> ") + } - sb.WriteString( "*" ) - utils.WriteMDv2Text( sb, (*b.Name)[ *lang ] ) - sb.WriteString( "*" ) + sb.WriteString("*") + utils.WriteMDv2Text(sb, (*b.Name)[*lang]) + sb.WriteString("*") - if b.NextStop() != nil { - sb.WriteString( " \\> " ) - utils.WriteMDv2Text( sb, (*b.NextStop().Name)[ *lang ] ) - } + if b.NextStop() != nil { + sb.WriteString(" \\> ") + utils.WriteMDv2Text(sb, (*b.NextStop().Name)[*lang]) + } - sb.WriteString( "\n" ) + sb.WriteString("\n") } -func ( this QueryResult ) Message() ( string, error ) { +func (this QueryResult) Message() (string, error) { - if this.Error != nil { - return "", this.Error - } + if this.Error != nil { + return "", this.Error + } - sb := strings.Builder{} + sb := strings.Builder{} - if this.Schedules == nil { + if this.Schedules == nil { - q := *this.Query + q := *this.Query - if len( *q.Results ) == 0 { - terms := make( []string, len(*q.SearchTerms), len(*q.SearchTerms) ) - for i, term := range *q.SearchTerms { - terms[i] = term.Org - } - return "", fmt.Errorf( "Not Found: \"%s\"", strings.Join( terms, "\", \"" ) ) - } + if len(*q.Results) == 0 { + terms := make([]string, len(*q.SearchTerms), len(*q.SearchTerms)) + for i, term := range *q.SearchTerms { + terms[i] = term.Org + } + return "", fmt.Errorf("Not Found: \"%s\"", strings.Join(terms, "\", \"")) + } - if q.Key == "" { - sort.Sort( query.ByKey( *q.Results ) ) - for _, entry := range *q.Results { - busStop := any( entry ).( *BusStop ) - utils.WriteMDv2Text( &sb, busStop.RouteId ) - sb.WriteString( " " ) - writeShortRoute( &this.Lang, &sb, busStop ) - } - } else if 1 == len( *q.SearchTerms ) { + if q.Key == "" { + sort.Sort(query.ByKey(*q.Results)) + for _, entry := range *q.Results { + busStop := any(entry).(*BusStop) + utils.WriteMDv2Text(&sb, busStop.RouteId) + sb.WriteString(" ") + writeShortRoute(&this.Lang, &sb, busStop) + } + } else if 1 == len(*q.SearchTerms) { - // Route listing - st := map[string] *BusStop{} - keys := [] string{} + // Route listing + busLines := map[string]map[string]*BusStop{} - for _, entry := range *q.Results { + for _, entry := range *q.Results { - busStop := any( entry ).( *BusStop ) - if _, ok := st[ busStop.Direction ]; ok { - continue - } + busStop := any(entry).(*BusStop) + busRoutes := busLines[busStop.ReferenceId] - st[ busStop.Direction ] = busStop - keys = append( keys, busStop.Direction ) + if busRoutes == nil { + busRoutes = make(map[string]*BusStop) + busLines[busStop.ReferenceId] = busRoutes + } - for st[ busStop.Direction ].PrevStop() != nil { - st[ busStop.Direction ] = st[ busStop.Direction ].PrevStop() - } - } + if _, ok := busRoutes[busStop.Direction]; ok { + continue + } - sort.Strings( keys ) + busRoutes[busStop.Direction] = busStop - for _, d := range keys { - b := st[ d ] - utils.WriteMDv2Text( &sb, q.Key ) + for busRoutes[busStop.Direction].PrevStop() != nil { + busRoutes[busStop.Direction] = busRoutes[busStop.Direction].PrevStop() + } + } - if d == "O" { - sb.WriteString( "↑" ) - } else if d == "I" { - sb.WriteString( "↓" ) - } else { - sb.WriteString( "\\?" ) - } - sb.WriteString( "\n " ) + // Sort Bus Lines + lineKeys := make([]string, 0, len(busLines)) + for k := range busLines { + lineKeys = append(lineKeys, k) + } - for { - utils.WriteMDv2Text( &sb, (*b.Name)[ this.Lang ] ) - b = b.NextStop() - if b == nil { - break - } + sort.Slice(lineKeys, func(i, j int) bool { + ai, an := splitKey(lineKeys[i]) + bi, bn := splitKey(lineKeys[j]) - sb.WriteString( " \\> " ) - } - sb.WriteString( "\n" ) - } + // different base → normal string compare + if ai != bi { + return ai < bi + } - } else { - return "", fmt.Errorf( "%s", "Unreachable condition occured!?" ) - } - } else { - if 0 < len( *this.Schedules ) { + // same base: base key comes first + if an == -1 && bn != -1 { + return true + } + if an != -1 && bn == -1 { + return false + } - busStops := [] *BusStop{} + // both have suffix → numeric compare + return an < bn + }) - for b, _ := range *this.Schedules { - busStops = append( busStops, b ) - } + for _, lineKey := range lineKeys { + busRoutes := busLines[lineKey] - sort.Sort( ByRoute( busStops ) ) + // Sort route directions + dirKeys := make([]string, 0, len(busRoutes)) + for k := range busRoutes { + dirKeys = append(dirKeys, k) + } - for _, busStop := range busStops { - buses := (*this.Schedules)[ busStop ] + sort.Strings(dirKeys) - writeShortRoute( &this.Lang, &sb, busStop ) - for _, bus := range buses.Buses { - sb.WriteString( " \\* " ) - if bus.ETAText == "" { - utils.WriteMDv2Text( &sb, bus.ETDText ) - } else { - utils.WriteMDv2Text( &sb, bus.ETAText ) - } - sb.WriteString( "\n" ) - } - sb.WriteString( "\n" ) - } - } else { - utils.WriteMDv2Text( &sb, "Schedules are empty...perhaps Out of Service Time?" ) - } - } + for _, d := range dirKeys { + b := busRoutes[d] - return sb.String(), nil + utils.WriteMDv2Text(&sb, lineKey) + + if d == "O" { + sb.WriteString("↑") + } else if d == "I" { + sb.WriteString("↓") + } else { + sb.WriteString("\\?") + } + sb.WriteString("\n ") + + for { + utils.WriteMDv2Text(&sb, (*b.Name)[this.Lang]) + b = b.NextStop() + if b == nil { + break + } + + sb.WriteString(" \\> ") + } + sb.WriteString("\n") + } + } + + } else { + return "", fmt.Errorf("%s", "Unreachable condition occured!?") + } + } else { + if 0 < len(*this.Schedules) { + + busStops := []*BusStop{} + + for b, _ := range *this.Schedules { + busStops = append(busStops, b) + } + + sort.Sort(ByRoute(busStops)) + + for _, busStop := range busStops { + buses := (*this.Schedules)[busStop] + + writeShortRoute(&this.Lang, &sb, busStop) + for _, bus := range buses.Buses { + sb.WriteString(" \\* ") + if bus.ETAText == "" { + utils.WriteMDv2Text(&sb, bus.ETDText) + } else { + utils.WriteMDv2Text(&sb, bus.ETAText) + } + sb.WriteString("\n") + } + sb.WriteString("\n") + } + } else { + utils.WriteMDv2Text(&sb, "Schedules are empty...perhaps Out of Service Time?") + } + } + + return sb.String(), nil +} + +func splitKey(s string) (base string, num int) { + parts := strings.SplitN(s, "-", 2) + if len(parts) == 1 { + return s, -1 // base key, no suffix + } + + n, err := strconv.Atoi(parts[1]) + if err != nil { + return s, -1 // fallback: treat as base + } + + return parts[0], n } diff --git a/datasources/mtr/bus/busstops.go b/datasources/mtr/bus/busstops.go index aa0410b..da47e13 100644 --- a/datasources/mtr/bus/busstops.go +++ b/datasources/mtr/bus/busstops.go @@ -4,7 +4,6 @@ import ( "encoding/csv" "fmt" "io" - "log" "net/http" "path/filepath" "strconv" @@ -63,21 +62,16 @@ func readBusStopData(r io.Reader) (*map[string]*BusStop, error) { } } - // Ignoring special route for now as getSchedule does not reflect - // which bus is a special route - if entry.ReferenceId != entry.RouteId { - log.Printf("Ignoring special Route: %s", entry.ReferenceId) - continue - } + entryId := fmt.Sprintf("%s:%s", entry.ReferenceId, entry.StationId) - if busStops[entry.StationId] != nil { + if busStops[entryId] != nil { return nil, fmt.Errorf("Duplicated entry %+v", entry) } - routeDir, hasKey := routeStops[entry.RouteId] + routeDir, hasKey := routeStops[entry.ReferenceId] if !hasKey { - routeStops[entry.RouteId] = map[string]map[int]*BusStop{} - routeDir = routeStops[entry.RouteId] + routeStops[entry.ReferenceId] = map[string]map[int]*BusStop{} + routeDir = routeStops[entry.ReferenceId] } route, hasKey := routeDir[entry.Direction] @@ -94,7 +88,7 @@ func readBusStopData(r io.Reader) (*map[string]*BusStop, error) { entry.RouteStops = &route entry.Reload() - busStops[entry.StationId] = &entry + busStops[entryId] = &entry } return &busStops, nil } diff --git a/datasources/mtr/bus/query.go b/datasources/mtr/bus/query.go index a337812..3e9ed04 100644 --- a/datasources/mtr/bus/query.go +++ b/datasources/mtr/bus/query.go @@ -1,59 +1,66 @@ package bus import ( - "strings" - query "github.com/tgckpg/golifehk/query" + "log" + "strings" + + query "github.com/tgckpg/golifehk/query" ) -func Query( lang string, message string ) query.IQueryResult { +func Query(lang string, message string) query.IQueryResult { - var qBusStops *query.QueryObject - var err error + var qBusStops *query.QueryObject + var err error - qr := QueryResult{ Lang: lang } - busStops, err := getBusStops() - if err != nil { - qr.Error = err - goto qrReturn - } + qr := QueryResult{Lang: lang} + busStops, err := getBusStops() + if err != nil { + qr.Error = err + goto qrReturn + } - qBusStops, err = query.Parse( strings.ToUpper( message ), busStops ) - if err != nil { - qr.Error = err - goto qrReturn - } + qBusStops, err = query.Parse(strings.ToUpper(message), busStops) + if err != nil { + qr.Error = err + goto qrReturn + } - qr.Query = qBusStops - if 0 < len( *qBusStops.Results ) && 1 < len( *qBusStops.SearchTerms ) { - schedules, err := getSchedule( lang, qBusStops.Key ) - if err != nil { - qr.Error = err - goto qrReturn - } + qr.Query = qBusStops + if 0 < len(*qBusStops.Results) && 1 < len(*qBusStops.SearchTerms) { + schedules, err := getSchedule(lang, qBusStops.Key) + if err != nil { + qr.Error = err + goto qrReturn + } - if len( schedules.BusStops ) == 0 { - qr.Schedules = &map[*BusStop] *BusStopBuses{} - goto qrReturn - } + if len(schedules.BusStops) == 0 { + qr.Schedules = &map[*BusStop]*BusStopBuses{} + goto qrReturn + } - matches := map[*BusStop] *BusStopBuses{} - for _, entry := range *qBusStops.Results { - busStop := any( entry ).( *BusStop ) + matches := map[*BusStop]*BusStopBuses{} + for _, entry := range *qBusStops.Results { + busStop := any(entry).(*BusStop) - for _, busStopSch := range schedules.BusStops { - if busStopSch.BusStopId == busStop.StationId { - matches[busStop] = &busStopSch - break - } - } + for _, busStopSch := range schedules.BusStops { + if busStopSch.BusStopId == busStop.StationId { + if busStop.RouteId != busStop.ReferenceId { + // There were no indicator for special routes from getSchedule API + log.Printf("Ignoring special route matches: %s", busStop.ReferenceId) + continue + } + matches[busStop] = &busStopSch + break + } + } - } + } - qr.Schedules = &matches - } + qr.Schedules = &matches + } - qrReturn: - var iqr query.IQueryResult - iqr = &qr - return iqr +qrReturn: + var iqr query.IQueryResult + iqr = &qr + return iqr }