patternMinor
Building a Simple Menu With Perl
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?
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
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:
Then the code in new_from_list() would take those arguments and build the proper attribute tree for new():
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.
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.