HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Building a Simple Menu With Perl

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simpleperlwithbuildingmenu

Problem

I wrote a Perl script to implement a simple menu where you can call subroutines for each menu option. I created a Menu class that can be used to create new menus and a print method that will start the menu.

Is there any way I could simplify this?

# Menu.pm

#!/usr/bin/perl

package Menu;

use strict;
use warnings;

# Menu constructor
sub new {

    # Unpack input arguments
    my $class       = shift;
    my (%args)      = @_;
    my $title       = $args{title};
    my $choices_ref = $args{choices};
    my $noexit      = $args{noexit};

    # Bless the menu object
    my $self = bless {
        title   => $title,
        choices => $choices_ref,
        noexit  => $noexit,
    }, $class;

    return $self;
}

# Print the menu
sub print {

    # Unpack input arguments
    my $self    = shift;
    my $title   =   $self->{title  };
    my @choices = @{$self->{choices}};
    my $noexit  =   $self->{noexit };

    # Print menu
    for (;;) {

        # Clear the screen
        system 'cls';

        # Print menu title
        print "========================================\n";
        print "    $title\n";
        print "========================================\n";

        # Print menu options
        my $counter = 0;
        for my $choice(@choices) {
            printf "%2d. %s\n", ++$counter, $choice->{text};
        }
        printf "%2d. %s\n", '0', 'Exit' unless $noexit;

        print "\n?: ";

        # Get user input
        chomp (my $input = );

        print "\n";

        # Process input
        if ($input =~ m/\d+/ && $input >= 1 && $input ();
        } elsif ($input =~ m/\d+/ && !$input && !$noexit) {
            print "Exiting . . .\n";
            exit 0;
        } else {
            print "Invalid input.\n\n";
            system 'pause';
        }
    }
}

1;


I wrote up a quick test script to test the module:

```
# test.pl

#!/usr/bin/perl

use strict;
use warnings;

use Menu;

my $menu1;
my $menu2;

# define menu1 choices
my @menu1_choice

Solution

Making perl APIs convenient is a bit of an art form, and perl gives you a lot to work with.

If you just want to streamline your code a bit, you could make a convenience method for the common cases like this:

my $menu1= Menu->new_from_list(
  "Menu1",
  "Choice1"     => sub { print "I did something!\n"; },
  "Choice2"     => sub { print "I did something else!\n"; },
  "Go to Menu2" => sub { $menu2->print(); },
  "noexit"
);


Then the code in new_from_list() would take those arguments and build the proper attribute tree for new():

sub new_from_list {
  my $class= shift;
  my $title= shift;
  my @choices;
  my $noexit= 0;
  while (@_ > 1) {
    push @choices, { text => shift, code => shift };
  }
  if (@_) {
    if ($_[0] eq 'noexit') {
      $noexit= 1;
    }
    else {
      die "unknown final argument to new_from_list: $_[0]"
    }
  }
  return $class->new( title => $title, choices => \@choices, noexit => $noexit );
}


The new_from_list API can't be extended easily in the future, but that's ok because it just handles a common case, and you can fall back to the normal constructor any time you need to.

Also, for simplifying the code needed to define Menu class, consider using a framework like Moo.

package Menu;
use Moo;

has title   => ( is => 'rw', required => 1 );

has choices => ( is => 'rw', required => 1 );

# choices is an arrayref.  This convenience method returns it as a list
sub choices_list { @{ shift->choices } }

has noexit  => ( is => 'rw' );

sub print {
  my $self= shift;
  ...
  for my $choice ($self->choices_list) {
    ...
  }
  ...
}

Code Snippets

my $menu1= Menu->new_from_list(
  "Menu1",
  "Choice1"     => sub { print "I did something!\n"; },
  "Choice2"     => sub { print "I did something else!\n"; },
  "Go to Menu2" => sub { $menu2->print(); },
  "noexit"
);
sub new_from_list {
  my $class= shift;
  my $title= shift;
  my @choices;
  my $noexit= 0;
  while (@_ > 1) {
    push @choices, { text => shift, code => shift };
  }
  if (@_) {
    if ($_[0] eq 'noexit') {
      $noexit= 1;
    }
    else {
      die "unknown final argument to new_from_list: $_[0]"
    }
  }
  return $class->new( title => $title, choices => \@choices, noexit => $noexit );
}
package Menu;
use Moo;

has title   => ( is => 'rw', required => 1 );

has choices => ( is => 'rw', required => 1 );

# choices is an arrayref.  This convenience method returns it as a list
sub choices_list { @{ shift->choices } }

has noexit  => ( is => 'rw' );


sub print {
  my $self= shift;
  ...
  for my $choice ($self->choices_list) {
    ...
  }
  ...
}

Context

StackExchange Code Review Q#80755, answer score: 2

Revisions (0)

No revisions yet.