如何在 Perl 中构建一个简单的菜单? [英] How can I build a simple menu in Perl?

查看:23
本文介绍了如何在 Perl 中构建一个简单的菜单?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个需要一些基本菜单功能的 Perl 脚本.最后,我希望每个菜单都有几个选项,然后是返回上一个菜单或退出的选项.

I'm working on a Perl script that requires some basic menu functionality. Ultimately I would like each menu to have a few options and then the option to either return to the previous menu or exit.

示例:

这是一个菜单:

  1. 选择 1
  2. 选择 2
  3. 返回上一级菜单
  4. 退出

选择一个选项:

我目前有一个菜单子程序来制作菜单,但没有功能允许它返回上一个菜单.

I currently have a menu subroutine making the menus, but there is no functionality allowing it to go back to the previous menu.

    sub menu
    {
        for (;;) {
            print "--------------------
";
            print "$_[0]
";
            print "--------------------
";
            for (my $i = 0; $i < scalar(@{ $_[1]}); $i++) {
                print $i + 1, ".	 ${ $_[1] }[$i]
";
            }
            print "
?: ";
            my $i = <STDIN>; chomp $i;
            if ($i && $i =~ m/[0-9]+/ && $i <= scalar(@{ $_[1]})) {
                return ${ $_[1] }[$i - 1];
            } else {
                print "
Invalid input.

";
            }
        }
    }

    # 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 语句进行所有处理,所以我将菜单变成了一个函数.

I don't want to hard code all of the menus and use if/elsif statements for all of the processing so I turned the menu into a function.

我的菜单目前看起来像这样...

My menus currently look like this...

菜单标题:

  1. 选择1
  2. 选择2
  3. 选择 3

?:(在此输入)

这个解决方案仍然不允许用户返回上一个菜单或退出.我正在考虑制作一个菜单类来处理菜单,但我仍然不太擅长面向对象的 Perl.这是一个只有几个菜单的小程序,因此使用复杂的菜单构建模块可能有点过头了.我希望我的代码尽可能简洁.

This solution still doesn't allow the user to go back to the previous menu or exit though. I was considering making a menu class to handle the menus, but I am still not very good with object oriented Perl. This is a small program with only a few menus so using a complex menu building module may be overkill. I would like to keep my code as light as possible.

感谢您的快速回复!然而,仍然存在一个问题.当我从Menu1"中选择一个选项并进入Menu2"时,我希望保存Menu1"中的选择以备后用:

Thanks for the quick responses! However there is still an issue. When I select an option from "Menu1" and it progresses to "Menu2" I would like the save the choice from "Menu1" for later use:

菜单 1:

  1. Choice1 <-- 存储选择值并转到下一个菜单
  2. 选择 2 <-- ...
  3. 退出 <-- 退出

菜单 2:

  1. Choice1 <-- 存储选择值并转到下一个菜单
  2. 选择 2 <-- ...
  3. 返回 <-- 返回上一级菜单重新选择值
  4. 退出 <-- 退出

选择 Choice1 或 Choice2 应将值存储在变量中以供以后使用进入下一个菜单.然后,如果您选择从 Menu2 返回到第一个菜单,它将为您提供重新选择您的选择并重新定义变量的选项.我试图避免使用全局变量,这使得这非常困难.

Selecting either Choice1 or Choice2 should store a value in a variable for later use and progress to the next menu. Then if you choose to go back to the first menu from Menu2, it will give you the option to reselect your choice and redefine the variable. I'm trying to avoid using global variables which makes this quite difficult.

在完成所有菜单并设置所有这些变量的值后,我想运行一个子程序来处理所有选择并打印最终输出.

After progressing through all of the menus and setting the values of all of these variables, I want to run a subroutine to process all of the choices and print a final output.

 sub main () {

   # DO MENU STUFF HERE

   # PROCESS RESULTS FROM MENU CHOICES
   my $output = process($menu1_choice, $menu2_choice, $menu3_choice, ... );
 }

此外,如果有人使用类或其他一些数据结构对此有面向对象的方法,尽管这可能有点矫枉过正,但我​​仍然很乐意看到它并尝试围绕这个想法!

Also if anyone has an object oriented approach to this using classes or some other data structure, although it may be overkill, I would still love to see it and try to wrap my head around the idea!

推荐答案

经过几个月的 Perl 编程后,我学到了更多关于如何处理对象的知识,并根据 Friedo 的回答编写了一个简单的面向对象的菜单构建模块.

After a few more months of programming with Perl I learned much more about how to deal with objects and wrote a simple object oriented menu building module based off of friedo's answer.

# 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 "========================================
";
        print "    $title
";
        print "========================================
";

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

        print "
?: ";

        # Get user input
        chomp (my $input = <STDIN>);

        print "
";

        # Process input
        if ($input =~ m/d+/ && $input >= 1 && $input <= $counter) {
            return $choices[$input - 1]{code}->();
        } elsif ($input =~ m/d+/ && !$input && !$noexit) {
            print "Exiting . . .
";
            exit 0;
        } else {
            print "Invalid input.

";
            system 'pause';
        }
    }
}

1;

使用此模块,您可以相对容易地构建菜单并将它们链接在一起.请参阅下面的使用示例:

Using this module you can build menus and link them together relatively easy. See example of usage below:

# 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!
"; }},
    { text => 'Choice2',
      code => sub { print "I did something else!
"; }},
    { 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!
"; }},
    { text => 'Choice2',
      code => sub { print "I did something else in menu 2!
"; }},
    { 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();

此代码将创建一个带有子菜单的简单菜单.进入子菜单后,您可以轻松返回上一级菜单.

This code will create a simple menu with a submenu. Once in the submenu you can easily go back to the previous menu.

感谢所有精彩的回答!他们真的帮我解决了这个问题,如果没有所有的帮助,我认为我不会得到这么好的解决方案!

Thanks for all of the great answers! They really helped me figure this out and I don't think i would have ended up with such a good solution without all the help!

更好的解决方案:

告别那些丑陋的哈希数组!

Menu.pm 和 Item.pm 模块内部的一些代码可能看起来有些混乱,但这种新设计使构建菜单本身的界面更加简洁和高效.

Some of the code internal to the Menu.pm and Item.pm modules may look slightly confusing, but this new design makes the interface of building the menus themselves much cleaner and more efficient.

经过一些仔细的代码修改并将单个菜单项变成它们自己的对象后,我能够创建一个更清晰的界面来创建菜单.这是我的新代码:

After some careful code reworking and making the individual menu items into their own objects I was able to create a much cleaner interface for creating menus. Here is my new code:

这是一个测试脚本,展示了如何使用模块构建菜单的示例.

This is a test script showing an example of how to use the modules to build menus.

# 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
";  system 'pause'; },
    'Test2' => sub { print "This is a test2
"; system 'pause'; },
    'Test3' => sub { print "This is a test3
"; 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 对象.对象存储在一个数组中,因此它们的顺序被保留.

The Menu.pm module is used to build menu objects. These menu objects can contain multiple Menu::Item objects. The objects are stored in an array so their order is preserved.

# 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 "========================================
";
        print "    $self->{_title}
";
        print "========================================
";

        # 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
", $index + 1, $name;
            } else {
                print "
";
            }
        }
        printf "%2d. %s
", 0, 'Exit' if $self->{_exit};

        # Get user input
        print "
?: ";
        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 "
";
                return $action->();
            }
        } elsif ($input =~ m/^d+$/ && $input == 0 && $self->{_exit}) {
            exit 0;
        }

        # Deal with invalid input
        print "
Invalid input.

";
        system 'pause';
    }
}

1;

Item.pm 模块必须存储在名为Menu"的子文件夹中,以便正确引用它.该模块允许您创建包含名称和子例程引用的 Menu::Item 对象.这些对象将是用户在菜单中选择的对象.

The Item.pm Module must be stored in a subfolder called "Menu" In order for it to be referenced properly. This module lets you create Menu::Item objects that contain a name and a subroutine reference. These objects will be what the user selects from in the menu.

# 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;

这个设计比我之前的设计有了很大的改进,让创建菜单变得更加容易和干净.

This design is a vast improvement over my previous design and makes creating menus much easier and cleaner.

告诉我你的想法.

有任何评论、想法或改进想法吗?

Any comments, thoughts, or improvement ideas?

这篇关于如何在 Perl 中构建一个简单的菜单?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆