Как я могу построить простое меню в Perl?
Я работаю над сценарием Perl, который требует некоторых основных функций меню. В конечном итоге я бы хотел, чтобы в каждом меню было несколько параметров, а затем возможность либо вернуться в предыдущее меню, либо выйти.
пример:
Это меню:
- Выбор 1
- Выбор 2
- Вернуться в предыдущее меню
- Выход
Выберите опцию:
В настоящее время у меня есть подпрограмма меню, создающая меню, но нет функции, позволяющей ей вернуться к предыдущему меню.
sub menu
{
for (;;) {
print "--------------------\n";
print "$_[0]\n";
print "--------------------\n";
for (my $i = 0; $i < scalar(@{ $_[1]}); $i++) {
print $i + 1, "\.\t ${ $_[1] }[$i]\n";
}
print "\n?: ";
my $i = <STDIN>; chomp $i;
if ($i && $i =~ m/[0-9]+/ && $i <= scalar(@{ $_[1]})) {
return ${ $_[1] }[$i - 1];
} else {
print "\nInvalid input.\n\n";
}
}
}
# Using the menu
my $choice1 = menu('Menu1 header', \@list_of_choices1);
# I would like this menu to give the option to go back to
# the first menu to change $choice1
my $choice2 = menu('Menu2 header', \@list_of_choices2);
Я не хочу жестко кодировать все меню и использовать операторы if / elsif для всей обработки, поэтому я превратил меню в функцию.
Мои меню в настоящее время выглядят так...
Заголовок меню:
- Choice 1
- Choice 2
- Choice3
?: (Введите ввод здесь)
Это решение по-прежнему не позволяет пользователю вернуться в предыдущее меню или выйти из него. Я думал о создании класса меню для обработки меню, но я все еще не очень хорош в объектно-ориентированном Perl. Это небольшая программа с несколькими меню, поэтому использование сложного модуля построения меню может оказаться излишним. Я хотел бы, чтобы мой код был максимально легким.
РЕДАКТИРОВАТЬ:
Спасибо за быстрые ответы! Однако есть еще проблема. Когда я выбираю опцию из "Menu1" и она переходит в "Menu2", я хотел бы сохранить выбор из "Menu1" для дальнейшего использования:
Menu1:
- Choice 1 <- сохранить значение, если оно выбрано, и перейти в следующее меню
- Choice 2 <-...
- Выход <- выход
menu2:
- Choice 1 <- сохранить значение, если оно выбрано, и перейти в следующее меню
- Choice 2 <-...
- Назад <- возврат в предыдущее меню для повторного выбора значения
- Выход <- выход
Выбор Choice 1 или Choice 2 должен сохранить значение в переменной для дальнейшего использования и перехода к следующему меню. Затем, если вы решите вернуться в первое меню из Menu2, это даст вам возможность повторно выбрать ваш выбор и переопределить переменную. Я пытаюсь избежать использования глобальных переменных, что делает это довольно сложным.
После прохождения всех меню и установки значений всех этих переменных я хочу запустить подпрограмму, чтобы обработать все варианты и вывести окончательный результат.
sub main () {
# DO MENU STUFF HERE
# PROCESS RESULTS FROM MENU CHOICES
my $output = process($menu1_choice, $menu2_choice, $menu3_choice, ... );
}
Также, если у кого-то есть объектно-ориентированный подход к этому с использованием классов или какой-либо другой структуры данных, хотя это может быть излишним, я все равно хотел бы увидеть это и попытаться обдумать идею!
6 ответов
После нескольких месяцев программирования на Perl я узнал гораздо больше о том, как обращаться с объектами, и написал простой объектно-ориентированный модуль построения меню, основанный на ответе Фридо.
# 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 = <STDIN>);
print "\n";
# Process input
if ($input =~ m/\d+/ && $input >= 1 && $input <= $counter) {
return $choices[$input - 1]{code}->();
} elsif ($input =~ m/\d+/ && !$input && !$noexit) {
print "Exiting . . .\n";
exit 0;
} else {
print "Invalid input.\n\n";
system 'pause';
}
}
}
1;
Используя этот модуль, вы можете создавать меню и соединять их относительно легко. Смотрите пример использования ниже:
# test.pl
#!/usr/bin/perl
use strict;
use warnings;
use Menu;
my $menu1;
my $menu2;
# define menu1 choices
my @menu1_choices = (
{ text => 'Choice1',
code => sub { print "I did something!\n"; }},
{ text => 'Choice2',
code => sub { print "I did something else!\n"; }},
{ text => 'Go to Menu2',
code => sub { $menu2->print(); }},
);
# define menu2 choices
my @menu2_choices = (
{ text => 'Choice1',
code => sub { print "I did something in menu 2!\n"; }},
{ text => 'Choice2',
code => sub { print "I did something else in menu 2!\n"; }},
{ text => 'Go to Menu1',
code => sub { $menu1->print(); }},
);
# Build menu1
$menu1 = Menu->new(
title => 'Menu1',
choices => \@menu1_choices,
);
# Build menu2
$menu2 = Menu->new(
title => 'Menu2',
choices => \@menu2_choices,
noexit => 1,
);
# Print menu1
$menu1->print();
Этот код создаст простое меню с подменю. Попав в подменю, вы можете легко вернуться в предыдущее меню.
Спасибо за все отличные ответы! Они действительно помогли мне разобраться в этом, и я не думаю, что я бы получил такое хорошее решение без всякой помощи!
ЛУЧШЕЕ РЕШЕНИЕ:
Попрощайтесь с этими уродливыми массивами хэшей!
Часть кода, встроенного в модули Menu.pm и Item.pm, может показаться немного запутанной, но этот новый дизайн делает интерфейс создания самих меню намного чище и эффективнее.
После некоторой тщательной обработки кода и превращения отдельных пунктов меню в свои собственные объекты, я смог создать намного более чистый интерфейс для создания меню. Вот мой новый код:
Это тестовый скрипт, показывающий пример использования модулей для создания меню.
# test.pl
#!/usr/bin/perl
# Always use these
use strict;
use warnings;
# Other use statements
use Menu;
# Create a menu object
my $menu = Menu->new();
# Add a menu item
$menu->add(
'Test' => sub { print "This is a test\n"; system 'pause'; },
'Test2' => sub { print "This is a test2\n"; system 'pause'; },
'Test3' => sub { print "This is a test3\n"; system 'pause'; },
);
# Allow the user to exit directly from the menu
$menu->exit(1);
# Disable a menu item
$menu->disable('Test2');
$menu->print();
# Do not allow the user to exit directly from the menu
$menu->exit(0);
# Enable a menu item
$menu->enable('Test2');
$menu->print();
Модуль Menu.pm используется для создания объектов меню. Эти объекты меню могут содержать несколько объектов Menu::Item. Объекты хранятся в массиве, поэтому их порядок сохраняется.
# Menu.pm
#!/usr/bin/perl
package Menu;
# Always use these
use strict;
use warnings;
# Other use statements
use Carp;
use Menu::Item;
# Menu constructor
sub new {
# Unpack input arguments
my ($class, $title) = @_;
# Define a default title
if (!defined $title) {
$title = 'MENU';
}
# Bless the Menu object
my $self = bless {
_title => $title,
_items => [],
_exit => 0,
}, $class;
return $self;
}
# Title accessor method
sub title {
my ($self, $title) = @_;
$self->{_title} = $title if defined $title;
return $self->{_title};
}
# Items accessor method
sub items {
my ($self, $items) = @_;
$self->{_items} = $items if defined $items;
return $self->{_items};
}
# Exit accessor method
sub exit {
my ($self, $exit) = @_;
$self->{_exit} = $exit if defined $exit;
return $self->{_exit};
}
# Add item(s) to the menu
sub add {
# Unpack input arguments
my ($self, @add) = @_;
croak 'add() requires name-action pairs' unless @add % 2 == 0;
# Add new items
while (@add) {
my ($name, $action) = splice @add, 0, 2;
# If the item already exists, remove it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
splice @{$self->{_items}}, $index, 1;
}
}
# Add the item to the end of the menu
my $item = Menu::Item->new($name, $action);
push @{$self->{_items}}, $item;
}
return 0;
}
# Remove item(s) from the menu
sub remove {
# Unpack input arguments
my ($self, @remove) = @_;
# Remove items
for my $name(@remove) {
# If the item exists, remove it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
splice @{$self->{_items}}, $index, 1;
}
}
}
return 0;
}
# Disable item(s)
sub disable {
# Unpack input arguments
my ($self, @disable) = @_;
# Disable items
for my $name(@disable) {
# If the item exists, disable it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
$self->{_items}->[$index]->active(0);
}
}
}
return 0;
}
# Enable item(s)
sub enable {
# Unpack input arguments
my ($self, @enable) = @_;
# Disable items
for my $name(@enable) {
# If the item exists, enable it
for my $index(0 .. $#{$self->{_items}}) {
if ($name eq $self->{_items}->[$index]->name()) {
$self->{_items}->[$index]->active(1);
}
}
}
}
# Print the menu
sub print {
# Unpack input arguments
my ($self) = @_;
# Print the menu
for (;;) {
system 'cls';
# Print the title
print "========================================\n";
print " $self->{_title}\n";
print "========================================\n";
# Print menu items
for my $index(0 .. $#{$self->{_items}}) {
my $name = $self->{_items}->[$index]->name();
my $active = $self->{_items}->[$index]->active();
if ($active) {
printf "%2d. %s\n", $index + 1, $name;
} else {
print "\n";
}
}
printf "%2d. %s\n", 0, 'Exit' if $self->{_exit};
# Get user input
print "\n?: ";
chomp (my $input = <STDIN>);
# Process user input
if ($input =~ m/^\d+$/ && $input > 0 && $input <= scalar @{$self->{_items}}) {
my $action = $self->{_items}->[$input - 1]->action();
my $active = $self->{_items}->[$input - 1]->active();
if ($active) {
print "\n";
return $action->();
}
} elsif ($input =~ m/^\d+$/ && $input == 0 && $self->{_exit}) {
exit 0;
}
# Deal with invalid input
print "\nInvalid input.\n\n";
system 'pause';
}
}
1;
Модуль Item.pm должен храниться в подпапке "Меню" для правильной ссылки на него. Этот модуль позволяет создавать объекты Menu::Item, которые содержат имя и ссылку на подпрограмму. Эти объекты будут тем, что пользователь выбирает в меню.
# Item.pm
#!/usr/bin/perl
package Menu::Item;
# Always use these
use strict;
use warnings;
# Menu::Item constructor
sub new {
# Unpack input arguments
my ($class, $name, $action) = @_;
# Bless the Menu::Item object
my $self = bless {
_name => $name,
_action => $action,
_active => 1,
}, $class;
return $self;
}
# Name accessor method
sub name {
my ($self, $name) = @_;
$self->{_name} = $name if defined $name;
return $self->{_name};
}
# Action accessor method
sub action {
my ($self, $action) = @_;
$self->{_action} = $action if defined $action;
return $self->{_action};
}
# Active accessor method
sub active {
my ($self, $active) = @_;
$self->{_active} = $active if defined $active;
return $self->{_active};
}
1;
Этот дизайн значительно улучшил мой предыдущий дизайн и делает создание меню намного проще и понятнее.
Дайте мне знать, что вы думаете.
Любые комментарии, мысли или идеи по улучшению?
Вы можете использовать такой модуль, как Term:: Choose:
use Term::Choose qw( choose );
my $submenus = {
menu1 => [ qw( s_1 s_2 s_3 ) ],
menu2 => [ qw( s_4 s_5 s_6 s_7) ],
menu3 => [ qw( s_8 s_9 ) ],
};
my @menus = ( qw( menu1 menu2 menu3 ) );
my $mm = 0;
MAIN: while ( 1 ) {
my $i = choose(
[ undef, @menus ],
{ layout => 3, undef => 'quit', index => 1, default => $mm }
);
last if ! $i;
if ( $mm == $i ) {
$mm = 0;
next MAIN;
}
else {
$mm = $i;
}
$i--;
SUB: while ( 1 ) {
my $choice = choose(
[ undef, @{$submenus->{$menus[$i]}} ],
{ layout => 3, undef => 'back' }
);
last SUB if ! defined $choice;
say "choice: $choice";
}
}
Если вы не хотите полностью использовать OO с этим, простой способ сделать это намного более гибким - позволить каждому выбору меню контролировать, как оно выполняется. Допустим, у каждого меню есть массив хешей, которые содержат текст меню и кодовую ссылку, которая реализует то, что делает меню.
use strict;
use warnings;
sub menu {
my @items = @_;
my $count = 0;
foreach my $item( @items ) {
printf "%d: %s\n", ++$count, $item->{text};
}
print "\n?: ";
while( my $line = <STDIN> ) {
chomp $line;
if ( $line =~ m/\d+/ && $line <= @items ) {
return $items[ $line - 1 ]{code}->();
}
print "\nInvalid input\n\n?: ";
}
}
my @menu_choices;
my @other_menu_choices;
@menu_choices = (
{ text => 'do something',
code => sub { print "I did something!\n" } },
{ text => 'do something else',
code => sub { print "foobar!\n" } },
{ text => 'go to other menu',
code => sub { menu( @other_menu_choices ) } }
);
@other_menu_choices = (
{ text => 'go back',
code => sub { menu( @menu_choices ) } }
);
menu( @menu_choices );
menu
Подпрограмма принимает массив параметров, и каждый параметр "знает", как выполнить свое собственное действие. Если вы хотите переключаться между меню, пункт меню просто вызывает menu
снова с другим списком опций, как в примере "вернуться назад" из @other_menu_choices
, Это делает связь между меню очень простой, а также легко добавлять опции выхода и тому подобное.
Чтобы сохранить этот код чистым и читаемым, для чего-либо, кроме тривиальных действий меню, используйте именованную ссылку на подпрограмму вместо анонимной ссылки подпрограммы. Например:
@another_menu_options = (
{ text => 'complicated action'
code => \&do_complicated_action
}
);
sub do_complicated_action {
...
}
Спасибо всем за ответы! Все три ответа были полезны для того, чтобы, наконец, прийти к моему решению. Я решил пойти с модулем Term::Choose, (спасибо sid_com за идею). Моя структура меню отличалась от той, которую вы изначально предлагали, и потребовалось немало времени, чтобы почесать голову, чтобы понять, как заставить ее делать именно то, что я хотел. Надеемся, что это решение поможет кому-то еще, кто сталкивается с подобной проблемой.
Я построил меню, как показано ниже:
(Я заменил мои переменные более общими именами, чтобы было легче следить)
#!/usr/bin/perl
use strict;
use warnings;
use Term::Choose qw(choose);
my @CHOICES1 = ('A','B','C');
my @CHOICES2 = ('1','2','3');
my @CHOICES3 = ('BLUE','YELLOW','GREEN');
# function to use the choices
sub some_function {
print "THIS IS SOME FUNCTION!\n";
print "Choice 1 is $_[0]\n";
print "Choice 2 is $_[1]\n";
print "Choice 3 is $_[2]\n";
print "Have a nice day! :)\n";
}
sub main() {
# clear the screen
# (for some reason the build in screen clear
# for the module was not working for me)
system ('cls');
# create menu object
my $menu = new Term::Choose();
# menu 1
for (;;) {
my $choice1 = $menu->choose(
[@CHOICES1, undef],
{
prompt => 'Select a choice1:',
undef => 'Exit',
layout => 3,
}
);
last if ! $choice1;
# submenu 1
for (;;) {
my $choice2 = $menu->choose(
[@CHOICES2, undef],
{
prompt => 'Select a choice2:',
undef => 'Back',
layout => 3,
}
);
last if ! $choice2;
# submenu2
for (;;) {
my $choice3 = $menu->choose(
[@CHOICES3, undef],
{
prompt => 'Select a choice3:',
undef => 'Back',
layout => 3,
}
);
last if ! $choice3;
# function operating on all choices
some_function($choice1, $choice2, $choice3);
return;
}
}
}
}
main();
Я все еще очень новичок в объектно-ориентированном Perl, так что это потребовало очень много времени, чтобы понять, и это может быть не идеально, но это делает работу. Дайте мне знать, если у вас есть идеи или улучшения!
Ниже приводится один подход. С каждым выбором связана подпрограмма. Когда выбор сделан, вызывается соответствующая подпрограмма. Здесь я использую анонимные подпрограммы, но вы также можете использовать ссылки на именованные подпрограммы.
use warnings; use strict;
sub menu {
my $args = shift;
my $title = $args->{title};
my $choices = $args->{choices};
while (1) {
print "--------------------\n";
print "$title\n";
print "--------------------\n";
for (my $i = 1; $i <= scalar(@$choices); $i++) {
my $itemHeading = $choices->[$i-1][0];
print "$i.\t $itemHeading\n";
}
print "\n?: ";
my $i = <STDIN>; chomp $i;
if ($i && $i =~ m/[0-9]+/ && $i <= scalar(@$choices)) {
&{$choices->[$i-1][1]}();
} else {
print "\nInvalid input.\n\n";
}
}
}
my $menus = {};
$menus = {
"1" => {
"title" => "Menu 1 header",
"choices" => [
[ "Choice 1" , sub { print "Choice 1 selected"; }],
[ "Choice 2" , sub { print "Choice 2 selected"; }],
[ "Menu 2" , sub { menu($menus->{2}); }],
[ "Exit" , sub { exit; }],
],
},
"2" => {
"title" => "Menu 2 header",
"choices" => [
[ "Choice 3" , sub { print "Choice 3 selected"; }],
[ "Choice 4" , sub { print "Choice 4 selected"; }],
[ "Menu 1" , sub { menu($menus->{1}); }],
[ "Exit" , sub { exit; }],
],
},
};
menu($menus->{1});
Я нашел этот старый модуль без каких-либо perldoc в моих модулях Perl... Пожалуйста, попробуйте...
#!/usr/bin/perl
BEGIN { $Curses::OldCurses = 1; }
use Curses;
use perlmenu;
&menu_init(0,"Select an Animal"); # Init menu
&menu_item("Collie","dog"); # Add item
&menu_item("Shetland","pony"); # Add item
&menu_item("Persian","cat"); # Add last item
$sel = &menu_display("Which animal?"); # Get user selection
if ($sel eq "dog") {print "Its Lassie!\n";}