From ff3b6800ec9e975b60853f42e437a857ad4d866f Mon Sep 17 00:00:00 2001 From: Birte Kristina Friesel Date: Sun, 20 Apr 2025 11:03:12 +0200 Subject: Rename motis to motis-m for consistancy with DBRIS, EFA, and HAFAS modules --- bin/motis | 553 ----------------------------------------------------------- bin/motis-m | 554 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 554 insertions(+), 553 deletions(-) delete mode 100755 bin/motis create mode 100755 bin/motis-m diff --git a/bin/motis b/bin/motis deleted file mode 100755 index 8e8794a..0000000 --- a/bin/motis +++ /dev/null @@ -1,553 +0,0 @@ -#!perl -use strict; -use warnings; -use 5.020; - -our $VERSION = '0.01'; - -use utf8; -use DateTime; -use Encode qw(decode); -use JSON; -use Getopt::Long qw(:config no_ignore_case); -use List::Util qw(min max); - -use Travel::Status::MOTIS; - -use Data::Dumper; - -my ( $date, $time ); -my $modes_of_transit; -my $developer_mode; -my $show_trip_ids; -my $use_cache = 1; -my $cache; -my ( $list_services, $service ); -my ( $json_output, $raw_json_output, $with_polyline ); - -my %known_mode_of_transit - = map { $_ => 1 } - ( - qw(TRANSIT TRAM SUBWAY FERRY AIRPLANE BUS COACH RAIL METRO HIGHSPEED_RAIL LONG_DISTANCE NIGHT_RAIL REGIONAL_FAST_RAIL REGIONAL_RAIL) - ); - -binmode( STDOUT, ':encoding(utf-8)' ); -for my $arg (@ARGV) { - $arg = decode( 'UTF-8', $arg ); -} - -my $output_bold = -t STDOUT ? "\033[1m" : q{}; -my $output_reset = -t STDOUT ? "\033[0m" : q{}; - -my $cf_first = "\e[38;5;11m"; -my $cf_mixed = "\e[38;5;208m"; -my $cf_second = "\e[0m"; #"\e[38;5;9m"; -my $cf_reset = "\e[0m"; - -GetOptions( - 'd|date=s' => \$date, - 'h|help' => sub { show_help(0) }, - 'i|show-trip-ids' => \$show_trip_ids, - 'm|modes-of-transit=s' => \$modes_of_transit, - 't|time=s' => \$time, - 's|service=s' => \$service, - 'V|version' => \&show_version, - 'cache!' => \$use_cache, - 'devmode' => \$developer_mode, - 'json' => \$json_output, - 'raw-json' => \$raw_json_output, - 'list' => \$list_services, -) or show_help(1); - -if ($list_services) { - printf( - "%-40s %-14s %-15s\n\n", - 'operator', 'abbr. (-s)', 'languages (-l)', - ); - - for my $service ( Travel::Status::MOTIS::get_services() ) { - printf( - "%-40s %-14s %-15s\n", - $service->{name}, $service->{shortname}, - join( q{ }, @{ $service->{languages} // [] } ), - ); - } - - exit 0; -} - -$service //= 'transitous'; - -if ($use_cache) { - my $cache_path = ( $ENV{XDG_CACHE_HOME} // "$ENV{HOME}/.cache" ) - . '/Travel-Status-MOTIS'; - - eval { - require Cache::File; - - $cache = Cache::File->new( - cache_root => $cache_path, - default_expires => '90 seconds', - lock_level => Cache::File::LOCK_LOCAL(), - ); - }; - - if ($@) { - $cache = undef; - } -} - -my ($input) = @ARGV; - -if ( not $input ) { - show_help(1); -} - -my %opt = ( - cache => $cache, - service => $service, - developer_mode => $developer_mode, -); - -if ( $input =~ m{ ^ (? [0-9.]+ ) : (? [0-9].+ ) $ }x ) { - $opt{stops_by_coordinate} = { - lat => $+{lat}, - lon => $+{lon}, - }; -} - -# Format: yyyymmdd_hh:mm_feed_id -elsif ( $input =~ m{^[0-9]{8}_[0-9]{2}:[0-9]{2}_} ) { - $opt{trip_id} = $input; -} - -# Format: feed_id -elsif ( $input =~ m{_} ) { - $opt{stop_id} = $input; -} -else { - $opt{stops_by_query} = $input; - - my $status = Travel::Status::MOTIS->new(%opt); - if ( my $err = $status->errstr ) { - say STDERR - "Request error while looking up '$opt{stops_by_query}': ${err}"; - exit 2; - } - - my $found; - for my $result ( $status->results ) { - if ( defined $result->id ) { - if ( lc( $result->name ) ne lc( $opt{stops_by_query} ) ) { - say $result->name; - } - - $opt{stop_id} = $result->id; - $found = 1; - last; - } - } - - if ( not $found ) { - say "Could not find stop '$opt{stops_by_query}'"; - exit 1; - } -} - -if ( $date or $time ) { - my $timestamp = DateTime->now( time_zone => 'local' ); - - if ($date) { - if ( $date - =~ m{ ^ (? \d{1,2} ) [.] (? \d{1,2} ) [.] (? \d{4})? $ }x - ) - { - $timestamp->set( - day => $+{day}, - month => $+{month} - ); - if ( $+{year} ) { - $timestamp->set( year => $+{year} ); - } - } - else { - say '--date must be specified as DD.MM.[YYYY]'; - exit 1; - } - } - - if ($time) { - if ( $time =~ m{ ^ (? \d{1,2} ) : (? \d{1,2} ) $ }x ) { - $timestamp->set( - hour => $+{hour}, - minute => $+{minute}, - second => 0, - ); - } - else { - say '--time must be specified as HH:MM'; - exit 1; - } - } - - $opt{timestamp} = $timestamp; -} - -if ( $modes_of_transit and $modes_of_transit eq 'help' ) { - say "Supported modes of transmit (-m / --modes-of-transit):"; - for my $mot ( - qw(TRANSIT TRAM SUBWAY FERRY AIRPLANE BUS COACH RAIL METRO HIGHSPEED_RAIL LONG_DISTANCE NIGHT_RAIL REGIONAL_FAST_RAIL REGIONAL_RAIL) - ) - { - say $mot; - } - - exit 0; -} - -if ($modes_of_transit) { - - # Passing unknown MOTs to the backend results in HTTP 422 Unprocessable Entity - my @mots = split( qr{, *}, $modes_of_transit ); - - my $found_unknown; - for my $mot (@mots) { - if ( not $known_mode_of_transit{$mot} ) { - $found_unknown = 1; - say STDERR - "-m / --modes-of-transit: unknown mode of transit '$mot'"; - } - } - - if ($found_unknown) { - say STDERR 'supported modes of transit are: ' - . join( q{, }, sort keys %known_mode_of_transit ); - exit 1; - } - - $opt{modes_of_transit} = [ grep { $known_mode_of_transit{$_} } @mots ]; -} - -sub show_help { - my ($code) = @_; - - print - "Usage: motis [-d dd.mm.yyy] [-t hh:mm] [-i] \n" - . "See also: man motis\n"; - - exit $code; -} - -sub show_version { - say "motis version ${VERSION}"; - - exit 0; -} - -sub spacer { - my ($len) = @_; - return ( $len % 2 ? q { } : q{} ) . ( q{ ·} x ( $len / 2 ) ); -} - -sub format_delay { - my ( $delay, $len ) = @_; - if ( $delay and $len ) { - return sprintf( "(%+${len}d)", $delay ); - } - return q{}; -} - -my $status = Travel::Status::MOTIS->new(%opt); - -if ( my $err = $status->errstr ) { - say STDERR "Request error: ${err}"; - exit 2; -} - -if ($raw_json_output) { - say JSON->new->convert_blessed->encode( $status->{raw_json} ); - exit 0; -} - -if ($json_output) { - if ( $opt{journey} ) { - say JSON->new->convert_blessed->encode( $status->result ); - } - else { - say JSON->new->convert_blessed->encode( [ $status->results ] ); - } - - exit 0; -} - -if ( $opt{stop_id} ) { - my $max_route_name = max map { length( $_->route_name ) } $status->results; - my $max_headsign - = max map { length( $_->headsign // q{} ) } $status->results; - my $max_delay = max map { length( $_->stopover->departure_delay // q{} ) } - $status->results; - my $max_track = max map { - length( $_->stopover->track // $_->stopover->scheduled_track // q{} ) - } $status->results; - - $max_delay += 1; - - my @results = map { $_->[1] } - sort { $a->[0] <=> $b->[0] } - map { - [ ( $_->stopover->departure // $_->stopover->arrival )->epoch, $_ ] - } $status->results; - - printf( "%s\n\n", $results[0]->stopover->stop->name ); - - for my $result (@results) { - printf( - "%s %s %${max_route_name}s %${max_headsign}s %${max_track}s\n", - $result->is_cancelled ? '--:--' - : $result->stopover->departure->strftime('%H:%M'), - $result->stopover->departure_delay ? sprintf( - "(%+${max_delay}d)", $result->stopover->departure_delay - ) - : q{ } x ( $max_delay + 2 ), - $result->route_name, - $result->headsign // q{???}, - $result->stopover->track // q{} - ); - - if ($show_trip_ids) { - say $result->id; - } - } -} -elsif ( $opt{trip_id} ) { - my $trip = $status->result; - - my $max_name = max map { length( $_->stop->name ) } $trip->stopovers; - my $max_track = max map { length( $_->track // q{} ) } $trip->stopovers; - my $max_delay - = max map { $_->delay ? length( $_->delay ) + 3 : 0 } $trip->stopovers; - - my $mark_stop = 0; - my $now = DateTime->now; - - for my $i ( reverse 1 .. ( scalar $trip->stopovers // 0 ) ) { - my $stop = ( $trip->stopovers )[ $i - 1 ]; - - if ( - not $stop->is_cancelled - and ( $stop->departure and $now <= $stop->departure - or $stop->arrival and $now <= $stop->arrival ) - ) - { - $mark_stop = $stop; - } - } - - printf( "%s am %s\n\n", - $trip->route_name, $trip->scheduled_arrival->strftime('%d.%m.%Y') ); - - for my $stop ( $trip->stopovers ) { - if ( $stop == $mark_stop ) { - print($output_bold); - } - - if ( $stop->is_cancelled ) { - print(' --:-- '); - } - elsif ( $stop->arrival and $stop->departure ) { - printf( '%s → %s', - $stop->arrival->strftime('%H:%M'), - $stop->departure->strftime('%H:%M'), - ); - } - elsif ( $stop->departure ) { - printf( ' %s', $stop->departure->strftime('%H:%M') ); - } - elsif ( $stop->arrival ) { - printf( '%s ', $stop->arrival->strftime('%H:%M') ); - } - else { - print(' '); - } - - printf( " %${max_delay}s", - format_delay( $stop->delay, $max_delay - 3 ) ); - printf( " %-${max_name}s %${max_track}s\n", - $stop->stop->name, $stop->track // q{} ); - - if ( $stop == $mark_stop ) { - print($output_reset); - } - } -} -elsif ( $opt{stops_by_coordinate} ) { - for my $result ( $status->results ) { - if ( defined $result->id ) { - printf( "%8d %s\n", $result->id, $result->name ); - } - } -} -elsif ( $opt{stops_by_query} ) { - for my $result ( $status->results ) { - if ( defined $result->id ) { - printf( "%8d %s\n", $result->id, $result->name ); - } - } -} - -__END__ - -=head1 NAME - -motis - An interface to the MOTIS routing services - -=head1 SYNOPSIS - -B [B<-s> I] [B<-d> I] [B<-t> I] [B<-i>] [I] I - -B [B<-s> I] [I] I - -B [B<-s> I] I - -B [B<-s> I] BI|IB<:>I - -=head1 VERSION - -version 0.01 - -=head1 DESCRIPTION - -B is an interface to MOTIS routing services. It can serve as an -arrival/departure board, request details about a specific trip, and -look up public transport stops by name or geolocation. The operating -mode depends on the contents of its non-option argument. - -=head2 Departure Board (I) - -Show departures at I. I may be given as a stop name or -stop id. For each departure, B shows - -=over - -=item * estimated departure time, - -=item * delay, if known, - -=item * trip route name, - -=item * headsign / destination if known, and - -=item * track, if known. - -=back - -=head2 Trip details (I) - -List intermediate stops of I (as given by the departure board when -invoked with B<-i> / B<--show-trip-ids>) with arrival/departure time, delay (if -available), track (if available), and stop name. Also includes some generic -trip information. - -=head2 Stop Search (BI|IB<:>I) - -List stop that match I or that are located in the vicinity of -IB<:>I geocoordinates with stop id and name. - -=head1 OPTIONS - -Values in brackets indicate options that only apply to the corresponding -operating mode(s). - -=over - -=item B<-d>, B<--date> I (departure board) - -Request departures on the specified date. -Default: today. - -=item B<-t>, B<--time> I (departure board) - -Request departures on the specified time. -Default: now. - -=item B<-i>, B<--show-trip-ids> (departure board) - -Show trip id for each listed arrival/departure. -These can be used to obtain details on individual trips with subsequent -B invocations. - -=item B<-m>, B<--modes-of-transit> I[,I,...] (departure board) - -Only return results for the specified modes of transit. -Use C<<-m help>> to get a list of supported modes of transit. - -=item B<--json> - -Print result(s) as JSON and exit. This is a dump of internal data structures -and not guaranteed to remain stable between minor versions. Please use the -Travel::Status::MOTIS(3pm) module if you need a proper API. - -=item B<--no-cache> - -By default, if the Cache::File module is available, server replies are cached -for 90 seconds in F<~/.cache/Travel-Status-MOTIS> (or a path relative to -C<$XDG_CACHE_HOME>, if set). Use this option to disable caching. You can use -B<--cache> to re-enable it. - -=item B<--raw-json> - -Print unprocessed API response as JSON and exit. -Useful for debugging and development purposes. - -=item B<-t>, B<--date> I (departure board) - -Request departures on or after the specified time. -Default: now. - -=item B<-V>, B<--version> - -Show version information and exit. - -=back - -=head1 EXIT STATUS - -0 upon success, 1 upon internal error, 2 upon backend error. - -=head1 CONFIGURATION - -None. - -=head1 DEPENDENCIES - -=over - -=item * Class::Accessor(3pm) - -=item * DateTime(3pm) - -=item * LWP::UserAgent(3pm) - -=back - -=head1 BUGS AND LIMITATIONS - -=over - -This module is mainly to debug the Travel::Status::MOTIS(3pm) module designed -for use in travelynx (L) and as -such might not contain functionality needed otherwise. - -=back - -=head1 AUTHOR - -Copyright (C) networkException Egit@nwex.deE - -Based on Travel::Status::DE::DBRIS - -Copyright (C) 2024-2025 Birte Kristina Friesel Ederf@finalrewind.orgE - -=head1 LICENSE - -This program is licensed under the same terms as Perl itself. diff --git a/bin/motis-m b/bin/motis-m new file mode 100755 index 0000000..15b9bc7 --- /dev/null +++ b/bin/motis-m @@ -0,0 +1,554 @@ +#!perl +use strict; +use warnings; +use 5.020; + +our $VERSION = '0.01'; + +use utf8; +use DateTime; +use Encode qw(decode); +use JSON; +use Getopt::Long qw(:config no_ignore_case); +use List::Util qw(min max); + +use Travel::Status::MOTIS; + +use Data::Dumper; + +my ( $date, $time ); +my $modes_of_transit; +my $developer_mode; +my $show_trip_ids; +my $use_cache = 1; +my $cache; +my ( $list_services, $service ); +my ( $json_output, $raw_json_output, $with_polyline ); + +my %known_mode_of_transit + = map { $_ => 1 } + ( + qw(TRANSIT TRAM SUBWAY FERRY AIRPLANE BUS COACH RAIL METRO HIGHSPEED_RAIL LONG_DISTANCE NIGHT_RAIL REGIONAL_FAST_RAIL REGIONAL_RAIL) + ); + +binmode( STDOUT, ':encoding(utf-8)' ); +for my $arg (@ARGV) { + $arg = decode( 'UTF-8', $arg ); +} + +my $output_bold = -t STDOUT ? "\033[1m" : q{}; +my $output_reset = -t STDOUT ? "\033[0m" : q{}; + +my $cf_first = "\e[38;5;11m"; +my $cf_mixed = "\e[38;5;208m"; +my $cf_second = "\e[0m"; #"\e[38;5;9m"; +my $cf_reset = "\e[0m"; + +GetOptions( + 'd|date=s' => \$date, + 'h|help' => sub { show_help(0) }, + 'i|show-trip-ids' => \$show_trip_ids, + 'm|modes-of-transit=s' => \$modes_of_transit, + 't|time=s' => \$time, + 's|service=s' => \$service, + 'V|version' => \&show_version, + 'cache!' => \$use_cache, + 'devmode' => \$developer_mode, + 'json' => \$json_output, + 'raw-json' => \$raw_json_output, + 'list' => \$list_services, +) or show_help(1); + +if ($list_services) { + printf( + "%-40s %-14s %-15s\n\n", + 'operator', 'abbr. (-s)', 'languages (-l)', + ); + + for my $service ( Travel::Status::MOTIS::get_services() ) { + printf( + "%-40s %-14s %-15s\n", + $service->{name}, $service->{shortname}, + join( q{ }, @{ $service->{languages} // [] } ), + ); + } + + exit 0; +} + +$service //= 'transitous'; + +if ($use_cache) { + my $cache_path = ( $ENV{XDG_CACHE_HOME} // "$ENV{HOME}/.cache" ) + . '/Travel-Status-MOTIS'; + + eval { + require Cache::File; + + $cache = Cache::File->new( + cache_root => $cache_path, + default_expires => '90 seconds', + lock_level => Cache::File::LOCK_LOCAL(), + ); + }; + + if ($@) { + $cache = undef; + } +} + +my ($input) = @ARGV; + +if ( not $input ) { + show_help(1); +} + +my %opt = ( + cache => $cache, + service => $service, + developer_mode => $developer_mode, +); + +if ( $input =~ m{ ^ (? [0-9.]+ ) : (? [0-9].+ ) $ }x ) { + $opt{stops_by_coordinate} = { + lat => $+{lat}, + lon => $+{lon}, + }; +} + +# Format: yyyymmdd_hh:mm_feed_id +elsif ( $input =~ m{^[0-9]{8}_[0-9]{2}:[0-9]{2}_} ) { + $opt{trip_id} = $input; +} + +# Format: feed_id +elsif ( $input =~ m{_} ) { + $opt{stop_id} = $input; +} +else { + $opt{stops_by_query} = $input; + + my $status = Travel::Status::MOTIS->new(%opt); + if ( my $err = $status->errstr ) { + say STDERR + "Request error while looking up '$opt{stops_by_query}': ${err}"; + exit 2; + } + + my $found; + for my $result ( $status->results ) { + if ( defined $result->id ) { + if ( lc( $result->name ) ne lc( $opt{stops_by_query} ) ) { + say $result->name; + } + + $opt{stop_id} = $result->id; + $found = 1; + last; + } + } + + if ( not $found ) { + say "Could not find stop '$opt{stops_by_query}'"; + exit 1; + } +} + +if ( $date or $time ) { + my $timestamp = DateTime->now( time_zone => 'local' ); + + if ($date) { + if ( $date + =~ m{ ^ (? \d{1,2} ) [.] (? \d{1,2} ) [.] (? \d{4})? $ }x + ) + { + $timestamp->set( + day => $+{day}, + month => $+{month} + ); + if ( $+{year} ) { + $timestamp->set( year => $+{year} ); + } + } + else { + say '--date must be specified as DD.MM.[YYYY]'; + exit 1; + } + } + + if ($time) { + if ( $time =~ m{ ^ (? \d{1,2} ) : (? \d{1,2} ) $ }x ) { + $timestamp->set( + hour => $+{hour}, + minute => $+{minute}, + second => 0, + ); + } + else { + say '--time must be specified as HH:MM'; + exit 1; + } + } + + $opt{timestamp} = $timestamp; +} + +if ( $modes_of_transit and $modes_of_transit eq 'help' ) { + say "Supported modes of transmit (-m / --modes-of-transit):"; + for my $mot ( + qw(TRANSIT TRAM SUBWAY FERRY AIRPLANE BUS COACH RAIL METRO HIGHSPEED_RAIL LONG_DISTANCE NIGHT_RAIL REGIONAL_FAST_RAIL REGIONAL_RAIL) + ) + { + say $mot; + } + + exit 0; +} + +if ($modes_of_transit) { + + # Passing unknown MOTs to the backend results in HTTP 422 Unprocessable Entity + my @mots = split( qr{, *}, $modes_of_transit ); + + my $found_unknown; + for my $mot (@mots) { + if ( not $known_mode_of_transit{$mot} ) { + $found_unknown = 1; + say STDERR + "-m / --modes-of-transit: unknown mode of transit '$mot'"; + } + } + + if ($found_unknown) { + say STDERR 'supported modes of transit are: ' + . join( q{, }, sort keys %known_mode_of_transit ); + exit 1; + } + + $opt{modes_of_transit} = [ grep { $known_mode_of_transit{$_} } @mots ]; +} + +sub show_help { + my ($code) = @_; + + print + "Usage: motis [-d dd.mm.yyy] [-t hh:mm] [-i] \n" + . "See also: man motis\n"; + + exit $code; +} + +sub show_version { + say "motis version ${VERSION}"; + + exit 0; +} + +sub spacer { + my ($len) = @_; + return ( $len % 2 ? q { } : q{} ) . ( q{ ·} x ( $len / 2 ) ); +} + +sub format_delay { + my ( $delay, $len ) = @_; + if ( $delay and $len ) { + return sprintf( "(%+${len}d)", $delay ); + } + return q{}; +} + +my $status = Travel::Status::MOTIS->new(%opt); + +if ( my $err = $status->errstr ) { + say STDERR "Request error: ${err}"; + exit 2; +} + +if ($raw_json_output) { + say JSON->new->convert_blessed->encode( $status->{raw_json} ); + exit 0; +} + +if ($json_output) { + if ( $opt{journey} ) { + say JSON->new->convert_blessed->encode( $status->result ); + } + else { + say JSON->new->convert_blessed->encode( [ $status->results ] ); + } + + exit 0; +} + +if ( $opt{stop_id} ) { + my $max_route_name = max map { length( $_->route_name ) } $status->results; + my $max_headsign + = max map { length( $_->headsign // q{} ) } $status->results; + my $max_delay = max map { length( $_->stopover->departure_delay // q{} ) } + $status->results; + my $max_track = max map { + length( $_->stopover->track // $_->stopover->scheduled_track // q{} ) + } $status->results; + + $max_delay += 1; + + my @results = map { $_->[1] } + sort { $a->[0] <=> $b->[0] } + map { + [ ( $_->stopover->departure // $_->stopover->arrival )->epoch, $_ ] + } $status->results; + + printf( "%s\n\n", $results[0]->stopover->stop->name ); + + for my $result (@results) { + printf( + "%s %s %${max_route_name}s %${max_headsign}s %${max_track}s\n", + $result->is_cancelled ? '--:--' + : $result->stopover->departure->strftime('%H:%M'), + $result->stopover->departure_delay ? sprintf( + "(%+${max_delay}d)", $result->stopover->departure_delay + ) + : q{ } x ( $max_delay + 2 ), + $result->route_name, + $result->headsign // q{???}, + $result->stopover->track // q{} + ); + + if ($show_trip_ids) { + say $result->id; + } + } +} +elsif ( $opt{trip_id} ) { + my $trip = $status->result; + + my $max_name = max map { length( $_->stop->name ) } $trip->stopovers; + my $max_track = max map { length( $_->track // q{} ) } $trip->stopovers; + my $max_delay + = max map { $_->delay ? length( $_->delay ) + 3 : 0 } $trip->stopovers; + + my $mark_stop = 0; + my $now = DateTime->now; + + for my $i ( reverse 1 .. ( scalar $trip->stopovers // 0 ) ) { + my $stop = ( $trip->stopovers )[ $i - 1 ]; + + if ( + not $stop->is_cancelled + and ( $stop->departure and $now <= $stop->departure + or $stop->arrival and $now <= $stop->arrival ) + ) + { + $mark_stop = $stop; + } + } + + printf( "%s am %s\n\n", + $trip->route_name, $trip->scheduled_arrival->strftime('%d.%m.%Y') ); + + for my $stop ( $trip->stopovers ) { + if ( $stop == $mark_stop ) { + print($output_bold); + } + + if ( $stop->is_cancelled ) { + print(' --:-- '); + } + elsif ( $stop->arrival and $stop->departure ) { + printf( '%s → %s', + $stop->arrival->strftime('%H:%M'), + $stop->departure->strftime('%H:%M'), + ); + } + elsif ( $stop->departure ) { + printf( ' %s', $stop->departure->strftime('%H:%M') ); + } + elsif ( $stop->arrival ) { + printf( '%s ', $stop->arrival->strftime('%H:%M') ); + } + else { + print(' '); + } + + printf( " %${max_delay}s", + format_delay( $stop->delay, $max_delay - 3 ) ); + printf( " %-${max_name}s %${max_track}s\n", + $stop->stop->name, $stop->track // q{} ); + + if ( $stop == $mark_stop ) { + print($output_reset); + } + } +} +elsif ( $opt{stops_by_coordinate} ) { + for my $result ( $status->results ) { + if ( defined $result->id ) { + printf( "%8d %s\n", $result->id, $result->name ); + } + } +} +elsif ( $opt{stops_by_query} ) { + for my $result ( $status->results ) { + if ( defined $result->id ) { + printf( "%8d %s\n", $result->id, $result->name ); + } + } +} + +__END__ + +=head1 NAME + +motis-m - Interface to MOTIS public transit services + +=head1 SYNOPSIS + +B [B<-s> I] [B<-d> I] [B<-t> I] [B<-i>] [I] I + +B [B<-s> I] [I] I + +B [B<-s> I] I + +B [B<-s> I] BI|IB<:>I + +=head1 VERSION + +version 0.01 + +=head1 DESCRIPTION + +B is an interface to MOTIS routing services. It can serve as an +arrival/departure board, request details about a specific trip, and +look up public transport stops by name or geolocation. The operating +mode depends on the contents of its non-option argument. + +=head2 Departure Board (I) + +Show departures at I. I may be given as a stop name or +stop id. For each departure, B shows + +=over + +=item * estimated departure time, + +=item * delay, if known, + +=item * trip route name, + +=item * headsign / destination if known, and + +=item * track, if known. + +=back + +=head2 Trip details (I) + +List intermediate stops of I (as given by the departure board when +invoked with B<-i> / B<--show-trip-ids>) with arrival/departure time, delay (if +available), track (if available), and stop name. Also includes some generic +trip information. + +=head2 Stop Search (BI|IB<:>I) + +List stop that match I or that are located in the vicinity of +IB<:>I geocoordinates with stop id and name. + +=head1 OPTIONS + +Values in brackets indicate options that only apply to the corresponding +operating mode(s). + +=over + +=item B<-d>, B<--date> I (departure board) + +Request departures on the specified date. +Default: today. + +=item B<-t>, B<--time> I (departure board) + +Request departures on the specified time. +Default: now. + +=item B<-i>, B<--show-trip-ids> (departure board) + +Show trip id for each listed arrival/departure. +These can be used to obtain details on individual trips with subsequent +B invocations. + +=item B<-m>, B<--modes-of-transit> I[,I,...] (departure board) + +Only return results for the specified modes of transit. +Use C<<-m help>> to get a list of supported modes of transit. + +=item B<--json> + +Print result(s) as JSON and exit. This is a dump of internal data structures +and not guaranteed to remain stable between minor versions. Please use the +Travel::Status::MOTIS(3pm) module if you need a proper API. + +=item B<--no-cache> + +By default, if the Cache::File module is available, server replies are cached +for 90 seconds in F<~/.cache/Travel-Status-MOTIS> (or a path relative to +C<$XDG_CACHE_HOME>, if set). Use this option to disable caching. You can use +B<--cache> to re-enable it. + +=item B<--raw-json> + +Print unprocessed API response as JSON and exit. +Useful for debugging and development purposes. + +=item B<-t>, B<--date> I (departure board) + +Request departures on or after the specified time. +Default: now. + +=item B<-V>, B<--version> + +Show version information and exit. + +=back + +=head1 EXIT STATUS + +0 upon success, 1 upon internal error, 2 upon backend error. + +=head1 CONFIGURATION + +None. + +=head1 DEPENDENCIES + +=over + +=item * Class::Accessor(3pm) + +=item * DateTime(3pm) + +=item * LWP::UserAgent(3pm) + +=back + +=head1 BUGS AND LIMITATIONS + +=over + +Currently, this script is mainly intended as a debugging aid for the +Travel::Status::MOTIS(3pm) module, which is in turn designed for use in +travelynx (L). It may not +provide functionality needed for use as a proper CLI public transit client. + +=back + +=head1 AUTHOR + +Copyright (C) 2025 networkException Egit@nwex.deE + +Copyright (C) 2025 Birte Kristina Friesel Ederf@finalrewind.orgE + +Based on Travel::Status::DE::DBRIS, which is (C) 2024-2025 Birte Kristina Friesel Ederf@finalrewind.orgE + +=head1 LICENSE + +This program is licensed under the same terms as Perl itself. -- cgit v1.2.3