#!/usr/bin/env perl use strict; use warnings; use 5.020; use utf8; use File::Slurp qw(read_file); use JSON; use List::Util qw(uniq); sub show_usage { my ($exit_code) = @_; say STDERR "Usage: umlauf-to-dot [line number] [limit] | dot -Tpng > umlauf.png"; exit $exit_code; } sub contained_in { my ( $value, @list ) = @_; for my $elem (@list) { if ( $elem eq $value ) { return 1; } } return; } sub format_train { my ( $train_no, $train ) = @_; return sprintf( '"%s %d\n%s\n%s"', $train->{type} // $train->{raw} // 'Zug', $train_no, $train->{route}{start} // $train->{route}{preStart} // '?', $train->{route}{end} // $train->{route}{postEnd} // '?' ); } binmode( STDOUT, ':encoding(utf-8)' ); if ( @ARGV < 1 ) { show_usage(1); } my ( $file, $line, $limit ) = @ARGV; my $file_content = read_file($file); my $json = JSON->new->utf8->decode($file_content); my $map = $json->{train}; sub build_cycle { my ( $line, $cycle_id ) = @_; my @queue = ( [ $line, 0 ] ); my @train_numbers; while (@queue) { my ( $train_number, $distance ) = @{ pop @queue }; push( @train_numbers, $train_number ); if ( $limit and $distance > $limit ) { next; } my @candidates; my $train = $map->{$train_number}; if ( my $c = $train->{cycle}{$cycle_id} ) { push( @candidates, @{ $c->{from} // [] } ); push( @candidates, @{ $c->{to} // [] } ); } @candidates = uniq @candidates; @candidates = grep { not contained_in( $_, @train_numbers, @queue ) } @candidates; if (@candidates) { printf( "# handle %s -> push %s\n", $train_number, join( " ", @candidates ) ); } push( @queue, map { [ $_, $distance + 1 ] } @candidates ); } my @output; for my $train_number (@train_numbers) { my $train = $map->{$train_number}; if ( my $c = $train->{cycle}{$cycle_id} ) { for my $from ( @{ $c->{from} // [] } ) { push( @output, sprintf( "%s -> %s;", format_train( $from, $map->{$from} ), format_train( $train_number, $map->{$train_number} ) ) ); } for my $to ( @{ $c->{to} // [] } ) { push( @output, sprintf( "%s -> %s;", format_train( $train_number, $map->{$train_number} ), format_train( $to, $map->{$to} ) ) ); } } if ( $train_number != $line ) { push( @output, sprintf( "%s [shape=box];", format_train( $train_number, $map->{$train_number} ) ) ); } } return @output; } my @cycle_ids; push( @cycle_ids, keys %{ $map->{$line}{cycle} // {} } ); say "digraph Umlauf {"; my @all_lines; for my $cycle_id (@cycle_ids) { push( @all_lines, build_cycle( $line, $cycle_id ) ); } say join( "\n", uniq @all_lines ); printf( "%s [style=bold];\n", format_train( $line, $map->{$line} ) ); say "}"