#!/usr/bin/env perl ## Copyright © 2009,2010 by Daniel Friesel ## License: WTFPL ## 0. You just DO WHAT THE FUCK YOU WANT TO. use strict; use warnings; use 5.010; use utf8; use Travel::Routing::DE::VRR; use Exception::Class; use Getopt::Long qw/:config no_ignore_case/; our $VERSION = '2.01'; my $ignore_info = 'Fahrradmitnahme'; my $efa; my ( @from, @to, @via, $from_type, $to_type, $via_type ); my $opt = { 'help' => sub { show_help(0) }, 'ignore-info' => \$ignore_info, 'from' => \@from, 'to' => \@to, 'version' => sub { say "efa version $VERSION"; exit 0 }, 'via' => \@via, }; binmode( STDOUT, ':encoding(utf-8)' ); binmode( STDERR, ':encoding(utf-8)' ); sub show_help { my ($exit_status) = @_; say 'Usage: efa [options] '; say 'See also: man efa'; exit $exit_status; } sub handle_efa_exception { my ($e) = @_; if ( $e->isa('Travel::Routing::DE::VRR::Exception::Setup') ) { if ( $e->message ) { printf STDERR ( "Error: %s (option '%s'): %s\n", $e->description, $e->option, $e->message ); } else { printf STDERR ( "Error: %s (option '%s', got '%s', want '%s')\n", $e->description, $e->option, $e->have, $e->want ); } exit 1; } if ( $e->isa('Travel::Routing::DE::VRR::Exception::Net') ) { printf STDERR ( "Error: %s: %s\n", $e->description, $e->http_response->as_string ); exit 2; } if ( $e->isa('Travel::Routing::DE::VRR::Exception::NoData') ) { printf STDERR ( 'Error: %s', $e->description ); exit 3; } if ( $e->isa('Travel::Routing::DE::VRR::Exception::Ambiguous') ) { printf STDERR ( "Error: %s for key %s. Specify one of %s\n", $e->description, $e->post_key, $e->possibilities ); exit 4; } if ( $e->isa('Travel::Routing::DE::VRR::Exception::NoConnections') ) { printf STDERR ( "Error: %s: %s\n", $e->description, $e->error ); exit 5; } printf STDERR ( "Uncatched exception: %s\n%s", ref($e), $e->trace ); exit 10; } sub check_for_error { my ($eval_error) = @_; if ( not defined $efa ) { if ( $eval_error and ref($eval_error) and $eval_error->isa('Travel::Routing::DE::VRR::Exception') ) { handle_efa_exception($eval_error); } elsif ($eval_error) { printf STDERR "Unknown Travel::Routing::DE::VRR error:\n${eval_error}"; exit 10; } else { say STDERR 'Travel::Routing::DE::VRR failed to return an object'; exit 10; } } return; } sub display_connection { my ($c) = @_; if ( $c->delay ) { printf( "# +%d, scheduled: %s -> %s\n", $c->delay, $c->departure_stime, $c->arrival_stime ); } for my $extra ( $c->extra ) { if ( not( length $ignore_info and $extra =~ /$ignore_info/i ) ) { say "# $extra"; } } printf( "%-5s ab %-30s %-20s %s\n", $c->departure_time, $c->departure_stop_and_platform, $c->train_line, $c->train_destination, ); printf( "%-5s an %s\n\n", $c->arrival_time, $c->arrival_stop_and_platform, ); return; } #<<< GetOptions( $opt, qw{ arrive|a=s bike|b date|d=s depart=s exclude|e=s@ extended-info|E from=s@{2} help|h ignore-info|I:s max-change|m=i prefer|P=s proximity|p include|i=s time|t=s timeout=i to=s@{2} version|v via=s@{2} walk-speed|w=s }, ) or show_help(1); #>>> if ( not( @from and @to ) ) { if ( @ARGV == 4 ) { ( @from[ 0, 1 ], @to[ 0, 1 ] ) = @ARGV; } elsif ( @ARGV == 6 ) { ( @from[ 0, 1 ], @via[ 0, 1 ], @to[ 0, 1 ] ) = @ARGV; } else { show_help(1); } } for my $pair ( [ \@from, \$from_type ], [ \@via, \$via_type ], [ \@to, \$to_type ], ) { next if ( not defined $pair->[0]->[1] ); if ( $pair->[0]->[1] =~ s{ ^ (? [^:]+ ) : \s* (? .+ ) $ } {$+{target}}x ) { given ( $+{type} ) { when ('addr') { ${ $pair->[1] } = 'address' } default { ${ $pair->[1] } = $+{type} } } } } if ( defined $opt->{'ignore-info'} and length( $opt->{'ignore-info'} ) == 0 ) { $opt->{'ignore-info'} = undef; } $efa = eval { Travel::Routing::DE::VRR->new( origin => [ @from, $from_type ], destination => [ @to, $to_type ], via => ( @via ? [ @via, $via_type ] : undef ), arrival_time => $opt->{arrive}, departure_time => $opt->{depart} // $opt->{time}, date => $opt->{date}, exclude => $opt->{exclude}, train_type => $opt->{include}, with_bike => $opt->{bike}, select_interchange_by => $opt->{prefer}, use_near_stops => $opt->{proximity}, walk_speed => $opt->{'walk-speed'}, max_interchanges => $opt->{'max-change'}, lwp_options => { timeout => $opt->{timeout} }, ); }; check_for_error($@); my @routes = $efa->routes; for my $i ( 0 .. $#routes ) { my $route = $routes[$i]; if ( $opt->{'extended-info'} ) { print '# ' . $route->duration; if ( $route->ticket_type ) { printf( ", class %s (%s€ / %s€)\n\n", $route->ticket_type, $route->fare_adult, $route->fare_child, ); } else { print "\n\n"; } } for my $c ( $route->parts ) { display_connection($c); } if ( $i != $#routes ) { print "---------\n\n"; } } __END__ =head1 NAME efa - unofficial efa.vrr.de command line client =head1 SYNOPSIS =over =item B B<--from> I I B<--to> I I [ I ] =item B [ I ] I I [ I I ] I I =back =head1 VERSION version 2.01 =head1 DESCRIPTION B is a command line client for the L web interface. It sends the specified information to the online form and displays the results. It should be noted that B, although using the web interface of a local transport association, is able to look up connections all over Germany. =head1 OPTIONS =over =item B<--from> I I Departure place =item B<--to> I I Arrival place =item B<--via> I I Travel via this place In case you want I to be an address or "point of interest", you can set it to 'addr:something' or 'poi:something'. =item B<-t>|B<--time>|B<--depart> I:I Journey start time =item B<-a>|B<--arrive> I:I Journey end time (overrides --time/--depart) =item B<-d>|B<--date> I
.I.[I] Journey date =item B<-b>|B<--bike> Choose connections allowing to carry a bike =item B<-e>|B<--exclude> I Exclude I (comma separated list). Possible transports: zug, s-bahn, u-bahn, stadtbahn, tram, stadtbus, regionalbus, schnellbus, seilbahn, schiff, ast, sonstige =item B<-E>|B<--extended-info> Display duration, ticket class and price for each route (if available) =item B<-m>|B<--max-change> I Print connections with at most I interchanges =item B<-P>|B<--prefer> I Prefer connections of I: =over =item * speed (default) The faster, the better =item * nowait Prefer connections with less interchanges =item * nowalk Prefer connections with less walking (at interchanges) =back =item B<-p>|B<--proximity> Take stops close to the stop/start into account and possibly use them instead =item B<-i>|B<--include> I Include connections using trains of type I, where I may be: =over =item * local (default) only take local trains ("Verbund-/Nahverkehrslinien"). Slow, but the cheapest method if you're not travelling long distance =item * ic Local trains + IC =item * ice All trains (local + IC + ICE) =back =item B<-w>|B<--walk-speed> I Set your walking speed to I. Accepted values: normal (default), fast, slow =item B<-I>|B<--ignore-info> [ I ] Ignore additional information matching I (default: /Fahrradmitnahme/) If I is not supplied, removes the default regex (-E nothing will be ignored) =item B<--timeout> I Set timeout for HTTP requests. Default: 60 seconds. =item B<--post> I=I Add I with I to the HTTP POST request sent to the EFA server. This can be used to use setting B does not yet cover, like C<--post lineRestriction=400> to also show IC and ICE trains. Note that B<--post> will be overridden by the standard efa options, such as B<--time> =item B<-v>|B<--version> Print version information =back =head1 EXIT STATUS 0 Everything went well 1 Invalid arguments, see error message 2 Network error, unable to send request 3 efa.vrr.de did not return any parsable data 4 efa.vrr.de error: ambiguous input 5 efa.vrr.de error: no connections found 10 Unknown Travel::Routing::DE::VRR error 255 Other internal error =head1 CONFIGURATION None. =head1 DEPENDENCIES This script requires perl 5.10 (or higher) with the following modules: =over =item * Class::Accessor =item * Exception::Class =item * LWP::UserAgent =item * XML::LibXML =back =head1 BUGS AND LIMITATIONS B cannot handle Unicode in its arguments, use plain ASCII. =head1 AUTHOR Copyright (C) 2009,2010 by Daniel Friesel Ederf@derf.homelinux.orgE =head1 LICENSE 0. You just DO WHAT THE FUCK YOU WANT TO.