diff --git a/go.mod b/go.mod index 1572cf5..d54f3b1 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/lithammer/shortuuid/v3 v3.0.7 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -32,21 +32,21 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/vektah/gqlparser/v2 v2.5.11 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect + github.com/vektah/gqlparser/v2 v2.5.16 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.23.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/domain/pln.go b/internal/domain/pln.go index 285284c..a4a88ee 100644 --- a/internal/domain/pln.go +++ b/internal/domain/pln.go @@ -29,25 +29,27 @@ type FlightSchedule struct { // Example: "ILS(0)" ILS string `json:"ils,omitempty"` - // ICAO code for the departure airport. - // Example: "ZBTJ" - DepartureAirport string `json:"departure_airport"` + // // ICAO code for the departure airport. + // // Example: "ZBTJ" + // DepartureAirport string `json:"departure_airport"` - // Scheduled departure time of the flight. This field may be formatted as HHMM or include date information. - // Example: "1845" - DepartureTime string `json:"departure_time,omitempty"` + // // Scheduled departure time of the flight. This field may be formatted as HHMM or include date information. + // // Example: "1845" + // DepartureTime string `json:"departure_time,omitempty"` - // Additional schedule information. This field may include special instructions or additional scheduling details. - // Example: "SI:TSN1845" - ScheduleInfo string `json:"schedule_info,omitempty"` + // // Additional schedule information. This field may include special instructions or additional scheduling details. + // // Example: "SI:TSN1845" + // ScheduleInfo string `json:"schedule_info,omitempty"` - // Estimated or scheduled arrival time of the flight. This field may be formatted as HHMM or include date information. - // Example: "2100" - ArrivalTime string `json:"arrival_time,omitempty"` + // // Estimated or scheduled arrival time of the flight. This field may be formatted as HHMM or include date information. + // // Example: "2100" + // ArrivalTime string `json:"arrival_time,omitempty"` - // ICAO code for the arrival airport. - // Example: "ZSPD" - ArrivalAirport string `json:"arrival_airport"` + // // ICAO code for the arrival airport. + // // Example: "ZSPD" + // ArrivalAirport string `json:"arrival_airport"` + + Waypoints []WayPoint `json:"waypoints"` // Additional comments or remarks about the flight schedule. This field may include any relevant notes or observations. // Example: "Special cargo handling required" @@ -57,6 +59,12 @@ type FlightSchedule struct { Reference string `json:"reference,omitempty"` } +type WayPoint struct { + ArrivalTime string + Airport string + DepartureTime string +} + func (f *FlightSchedule) Validate() error { if f.Date == "" { return fmt.Errorf("date is required") @@ -67,15 +75,5 @@ func (f *FlightSchedule) Validate() error { if f.AircraftReg == "" { return fmt.Errorf("aircraft registration is required") } - if f.DepartureAirport == "" { - return fmt.Errorf("departure airport is required") - } - if f.ArrivalAirport == "" { - return fmt.Errorf("arrival airport is required") - } - - if f.DepartureTime == "" && f.ArrivalTime == "" { - return fmt.Errorf("either departure time or arrival time is required") - } return nil } diff --git a/internal/parsers/schedule_parser.go b/internal/parsers/schedule_parser.go index 3806f5b..427ff3e 100644 --- a/internal/parsers/schedule_parser.go +++ b/internal/parsers/schedule_parser.go @@ -2,21 +2,19 @@ package parsers import ( "caatsm/internal/domain" + "caatsm/pkg/utils" "regexp" "strings" ) const ( - // DepartureCode = "dep" - // DepartureTime = "dep_time" - // ArrivalCode = "arr" AirportCode = "airport" Date = "date" Task = "task" IndexPattern = `(?P\(?L?[0-9]+\)?\.?)` DatePattern = `\s?(?P\d{2}\w{3})` TaskPattern = `\s?(?P[A-Z]\/[A-Z])` - WaypointPattern = `\s?(?P\d{4}(\(\d{2}\w{3}\))?)\/?(?P\w{3})\/?(?P\d{4}(\(\d{2}\w{3}\))?)` + WaypointPattern = `^(SI:)?(?P\d{4}(\(\d{2}[A-Z]{3}\))?)?\/?(?P[A-Z]{3})\/?(?P\d{4}(\(\d{2}[A-Z]{3}\))?)?$` FlightNumberPattern = `\s?(?P[0-9A-Z][0-9A-Z]\d{3,5})` RegisterPattern = `\s?(?PB\d{4})` ) @@ -38,31 +36,43 @@ var ( } ) -func FindWaypoints(message string) map[string]string { +func ExtractWaypoint(message string) *domain.WayPoint { matches := WaypointExpression.FindStringSubmatch(message) if matches == nil { return nil } - result := make(map[string]string) + data := make(map[string]string) for i, name := range WaypointExpression.SubexpNames() { if i != 0 && name != "" { - result[name] = matches[i] + data[name] = matches[i] } } + result := &domain.WayPoint{ + ArrivalTime: data[ArrivalTime], + Airport: data[AirportCode], + DepartureTime: data[DepartureTime], + } return result } func ParseLine(line string) *domain.FlightSchedule { + log := utils.GetSugaredLogger() cleanLine := strings.TrimSpace(line) words := strings.Split(cleanLine, " ") - var flightSchedule = &domain.FlightSchedule{} + var flightSchedule = &domain.FlightSchedule{ + Reference: line, + } var data map[string]string + if indexData := parse(words[0], IndexExpression); indexData != nil { + flightSchedule.Index = indexData[Index] + words = words[1:] + } + // Define the parsing strategy parseStrategy := []string{ - Index, Task, Date, FlightNumber, @@ -71,38 +81,81 @@ func ParseLine(line string) *domain.FlightSchedule { // Track parsed fields to avoid re-parsing parsed := make(map[string]bool) + var maxParsed int + // Parse each word in the line for i, word := range words { - if i > 0 { - parsed[Index] = true - } + // Check if all fields have been parsed for _, name := range parseStrategy { + // Skip if already parsed if parsed[name] { continue } + // Parse the word if data = parse(word, parserMap[name]); data != nil { - + // Update the flight schedule switch name { - case Index: - flightSchedule.Index = data[Index] - parsed[Index] = true case Task: flightSchedule.Task = data[Task] parsed[Task] = true + maxParsed = i case Date: flightSchedule.Date = data[Date] parsed[Date] = true + maxParsed = i case FlightNumber: flightSchedule.FlightNumber = data[FlightNumber] parsed[FlightNumber] = true + maxParsed = i case Register: flightSchedule.AircraftReg = data[Register] parsed[Register] = true + maxParsed = i } break } } + + } + if maxParsed+1 < len(words) { + flightSchedule.Waypoints = parseWaypoints(words[maxParsed+1:]) + } else { + log.Warn("No waypoints found") + flightSchedule.Comments = "No waypoints found" } return flightSchedule } + +func parseWaypoints(points []string) []domain.WayPoint { + log := utils.GetSugaredLogger() + if len(points) == 0 { + log.Warn("No waypoints found") + return nil + } + + //find first waypoint + var realWaypoints []string + for i, point := range points { + if parse(point, WaypointExpression) != nil { + realWaypoints = points[i:] + break + } + } + if len(realWaypoints) == 0 { + log.Warn("No waypoints found") + return nil + } + var waypoints []domain.WayPoint + for _, point := range realWaypoints { + log.Debugf("Parsing waypoint: %s", point) + if waypoint := ExtractWaypoint(point); waypoint != nil { + log.Debugf("Waypoint: %v", waypoint) + waypoints = append(waypoints, *waypoint) + } else { + log.Warnf("Failed to parse waypoint: %s", point) + } + } + log.Debugf("Found %d waypoints", len(waypoints)) + return waypoints +} diff --git a/internal/parsers/schedule_parser_test.go b/internal/parsers/schedule_parser_test.go index d50ae47..0c08666 100644 --- a/internal/parsers/schedule_parser_test.go +++ b/internal/parsers/schedule_parser_test.go @@ -105,16 +105,16 @@ var _ = Describe("Schedule Parser", func() { Describe("FindWaypoints", func() { It("should return the correct waypoints based on the message", func() { message := "1845(11JUN)TSN/2100" - waypoints := FindWaypoints(message) - Expect(waypoints).NotTo(BeNil()) - Expect(waypoints[ArrivalTime]).To(Equal("1845(11JUN)")) - Expect(waypoints[AirportCode]).To(Equal("TSN")) - Expect(waypoints[DepartureTime]).To(Equal("2100")) + waypoint := ExtractWaypoint(message) + Expect(waypoint).NotTo(BeNil()) + Expect(waypoint.ArrivalTime).To(Equal("1845(11JUN)")) + Expect(waypoint.Airport).To(Equal("TSN")) + Expect(waypoint.DepartureTime).To(Equal("2100")) }) It("should return nil if no waypoints are found", func() { - message := "1845TSN" - waypoints := FindWaypoints(message) + message := "18451TSN" + waypoints := ExtractWaypoint(message) Expect(waypoints).To(BeNil()) }) }) @@ -130,6 +130,10 @@ var _ = Describe("Schedule Parser", func() { // Expect(schedule.Task).To(Equal("1/1")) Expect(schedule.FlightNumber).To(Equal("FM9134")) Expect(schedule.AircraftReg).To(Equal("B2688")) + Expect(len(schedule.Waypoints)).To(Equal(2)) + Expect(schedule.Waypoints[0].Airport).To(Equal("TSN")) + Expect(schedule.Waypoints[0].DepartureTime).To(Equal("0100")) + Expect(schedule.Waypoints[1].Airport).To(Equal("SHA")) // Expect(schedule.PassengerConfig).To(Equal("1/1")) // Expect(schedule.ILS).To(Equal("ILS (00)")) // Expect(schedule.DepartureAirport).To(Equal("TSN"))