=encoding utf-8

=head1 NAME

Getopt::EX::Hashed - 为 Getopt::Long 提供哈希对象自动化

=head1 VERSION

Version 1.0602

=head1 SYNOPSIS

  # script/foo
  use App::foo;
  App::foo->new->run();

  # lib/App/foo.pm
  package App::foo;

  use Getopt::EX::Hashed; {
      Getopt::EX::Hashed->configure( DEFAULT => [ is => 'rw' ] );
      has start    => ' =i  s begin ' , default => 1;
      has end      => ' =i  e       ' ;
      has file     => ' =s@ f       ' , any => qr/^(?!\.)/;
      has score    => ' =i          ' , min => 0, max => 100;
      has answer   => ' =i          ' , must => sub { $_[1] == 42 };
      has mouse    => ' =s          ' , any => [ 'Frankie', 'Benjy' ];
      has question => ' =s          ' , any => qr/^(life|universe|everything)$/i;
  } no Getopt::EX::Hashed;

  sub run {
      my $app = shift;
      use Getopt::Long;
      $app->getopt or pod2usage();
      if ($app->answer == 42) {
          $app->question //= 'life';
          ...

=cut
=head1 DESCRIPTION

B<Getopt::EX::Hashed> 是一个模块，用于为 B<Getopt::Long> 及其兼容模块（包括 B<Getopt::EX::Long>）自动创建用于存储命令行选项值的哈希对象。该模块名称带有 B<Getopt::EX> 前缀，但目前它可独立于 B<Getopt::EX> 中的其他模块工作。

本模块的主要目标是将初始化与规范定义整合在同一处。它还提供了一个简单的校验接口。

当提供 C<is> 参数时，会自动生成访问器方法。如果同名函数已存在，程序将发生致命错误。对象销毁时，访问器将被移除。当同时存在多个对象时可能出现问题。

=head1 FUNCTION

=head2 B<has>

以如下形式声明选项参数。圆括号仅为清晰起见，可省略。

    has option_name => ( param => value, ... );

例如，要定义选项 C<--number>，它接受一个整数参数，同时也可用作 C<-n>，请按如下操作

    has number => spec => "=i n";

访问器将以第一个名称创建。在此示例中，访问器将定义为 C<< $app->number >>。

如果给出数组引用，可以一次声明多个名称。

    has [ 'left', 'right' ] => ( spec => "=i" );

如果名称以加号（C<+>）开头，给定参数将更新现有设置。

    has '+left' => ( default => 1 );

对于 C<spec> 参数，如果它是第一个参数，标签可以省略。

    has left => "=i", default => 1;

如果参数数量为奇数，第一个参数将被视为具有隐式标签：若为代码引用则为 C<action>，否则为 C<spec>。

可用的参数如下。

=over 7

=item [ B<spec> => ] I<string>

给出选项规范。仅当且仅当它是第一个参数时，可以省略 C<< spec => >> 标签。

在 I<string> 中，选项规范与别名以空白分隔，且可以任意顺序出现。

要声明一个名为 C<--start> 的选项，它接受一个整数值，并且也可以使用名称 C<-s> 和 C<--begin>，如下声明。

    has start => "=i s begin";

上述声明将被编译为如下字符串。

    start|s|begin=i

其符合 C<Getopt::Long> 的定义。当然，你也可以这样写：

    has start => "s|begin=i";

如果名称和别名包含下划线（C<_>），则会再定义一个把下划线替换为短横线（C<->）的别名。

    has a_to_z => "=s";

上述声明将被编译为如下字符串。

    a_to_z|a-to-z=s

如果不需要选项规范（spec），请将一个空字符串（或仅包含空白）作为值。没有 spec 字符串时，该成员不会被视为选项。

=item B<alias> => I<string>

也可以通过 B<alias> 参数指定额外的别名名称。这与 C<spec> 参数中的别名没有差别。

    has start => "=i", alias => "s begin";

=item B<is> => C<ro> | C<rw>

要生成访问器方法，需要 C<is> 参数。设置值 C<ro> 为只读，C<rw> 为读写。

读写访问器具有左值属性，因此可以被赋值。可如下使用：

    $app->foo //= 1;

这比下面这样写要简洁得多。

    $app->foo(1) unless defined $app->foo;

如果你想为后续所有成员创建访问器，使用 C<configure> 设置 C<DEFAULT> 参数。

    Getopt::EX::Hashed->configure( DEFAULT => [ is => 'rw' ] );

如果你不喜欢可赋值的存取器，请将 C<ACCESSOR_LVALUE> 参数配置为 0。由于存取器是在调用 C<new> 时生成的，该值对所有成员都有效。

=item B<default> => I<value> | I<coderef>

设置默认值。如果未提供默认值，成员将初始化为 C<undef>。

如果该值是对 ARRAY 或 HASH 的引用，则每次调用 C<new> 都会创建一个浅拷贝。这意味着复制的是引用本身，但其内容是共享的。修改数组或哈希的内容将影响所有实例。

如果提供了代码引用，它会在 B<new> 时被调用以获得默认值。当你希望在执行时而非声明时计算该值时，这很有效。如果你想定义默认动作，请使用 B<action> 参数。如果你想将代码引用设置为初始值，你必须指定一个返回代码引用的代码引用。

如果提供了对 SCALAR 的引用，选项值将存储在该引用所指向的数据中，而不是哈希对象成员中。在这种情况下，通过访问哈希成员无法获得期望的值。

=item [ B<action> => ] I<coderef>

参数 C<action> 接受用于处理该选项的代码引用。只有当它是第一个参数时，才能省略 C<< action => >> 标记。

调用时，哈希对象作为 C<$_> 传入。

    has [ qw(left right both) ] => '=i';
    has "+both" => sub {
        $_->{left} = $_->{right} = $_[1];
    };

你可以将其用于 C<< "<>" >> 以处理非选项参数。在这种情况下，spec 参数无关紧要且不是必需的。

    has ARGV => default => [];
    has "<>" => sub {
        push @{$_->{ARGV}}, $_[0];
    };

=back

以下参数均用于数据验证。首先，C<must> 是通用验证器，几乎可以实现任何规则。其他的是常见规则的快捷方式。

=over 7

=item B<must> => I<coderef> | [ I<coderef> ... ]

参数 C<must> 接受一个用于验证选项值的代码引用。它接受与 C<action> 相同的参数并返回布尔值。下面的示例中，选项 B<--answer> 只有在值为 42 时才有效。

    has answer => '=i',
        must => sub { $_[1] == 42 };

如果提供了多个代码引用，所有代码都必须返回真。

    has answer => '=i',
        must => [ sub { $_[1] >= 42 }, sub { $_[1] <= 42 } ];

=item B<min> => I<number>

=item B<max> => I<number>

为参数设置最小和最大限制。

=item B<any> => I<arrayref> | qr/I<regex>/ | I<coderef>

设置有效的字符串参数列表。每个项目可以是字符串、正则表达式引用或代码引用。当参数与给定列表中的任一项相同或匹配时即为有效。如果该值不是 arrayref，则将其视为单项列表（通常是 regexpref 或 coderef）。

以下声明几乎等价，只是第二个不区分大小写。

    has question => '=s',
        any => [ 'life', 'universe', 'everything' ];

    has question => '=s',
        any => qr/^(life|universe|everything)$/i;

如果你使用可选参数，别忘了在列表中包含默认值。否则会导致验证错误。

    has question => ':s',
        any => [ 'life', 'universe', 'everything', '' ];

=back

=head1 METHOD

=head2 B<new>

一个创建新的哈希对象的类方法。用默认值初始化所有成员，并按配置创建访问器方法。返回一个被祝福的哈希引用。如果启用了 LOCK_KEYS，则哈希键会被锁定。

=head2 B<optspec>

返回可传递给 C<GetOptions> 函数的选项规范列表。

    GetOptions($obj->optspec)

C<GetOptions> 具备通过将哈希引用作为第一个参数来将值存储到哈希中的能力，但这不是必需的。

=head2 B<getopt> [ I<arrayref> ]

调用在调用方上下文中定义的相应函数以处理选项。

    $obj->getopt

    $obj->getopt(\@argv);

上面的示例是以下代码的简写。

    GetOptions($obj->optspec)

    GetOptionsFromArray(\@argv, $obj->optspec)

=head2 B<use_keys> I<keys>

当启用 LOCK_KEYS 时，访问不存在的成员会导致错误。请在访问之前使用此方法声明新的成员键。

    $obj->use_keys( qw(foo bar) );

如果你想访问任意键，请解锁该对象。

    use Hash::Util 'unlock_keys';
    unlock_keys %{$obj};

你可以通过带有 C<LOCK_KEYS> 参数的 C<configure> 来更改此行为。

=head2 B<configure> B<label> => I<value>, ...

在创建对象之前，使用类方法 C<< Getopt::EX::Hashed->configure() >>；该信息按调用包分别存储。调用 C<new()> 之后，包级配置会被复制到对象中供其使用。使用 C<< $obj->configure() >> 更新对象级配置。

可用的配置参数如下。

=over 7

=item B<LOCK_KEYS> (default: 1)

锁定哈希键。这可防止因拼写错误或其他失误而创建非预期的哈希条目。

=item B<REPLACE_UNDERSCORE> (default: 1)

自动创建将下划线替换为连字符的选项别名。

=item B<REMOVE_UNDERSCORE> (default: 0)

自动创建移除下划线的选项别名。

=item B<GETOPT> (default: 'GetOptions')

=item B<GETOPT_FROM_ARRAY> (default: 'GetOptionsFromArray')

设置从 C<getopt> 方法调用的函数名。

=item B<ACCESSOR_PREFIX> (default: '')

当指定时，它会被前置到成员名以生成访问器方法。如果 C<ACCESSOR_PREFIX> 定义为 C<opt_>，则成员 C<file> 的访问器将是 C<opt_file>。

=item B<ACCESSOR_LVALUE> (default: 1)

如果为真，读写访问器具有 lvalue 属性。如果你不喜欢这种行为，请设为零。

=item B<DEFAULT>

设置默认参数。当调用 C<has> 时，DEFAULT 参数会插入到显式参数之前。若两者都有同一参数，则以显式参数为准。带有 C<+> 的增量调用不受影响。

DEFAULT 的典型用法是使用 C<is> 为随后的所有哈希条目准备访问器方法。声明 C<< DEFAULT => [] >> 可重置。

    Getopt::EX::Hashed->configure(DEFAULT => [ is => 'ro' ]);

=begin comment

=item B<INVALID_MSG> (default: built-in error message generator)

设置一个代码引用，用于在验证失败时生成错误信息。该代码引用接收与选项处理器相同的参数（$_[0] 是选项名，简单选项的值在 $_[1]，或哈希选项的值在 $_[1] 和 $_[2]）。默认函数会生成诸如“--option=value: option validation error”的消息。

    Getopt::EX::Hashed->configure(
        INVALID_MSG => sub {
            my $opt = shift;
            "Invalid value for --$opt: @_\n";
        }
    );

=end comment

=back

=head2 B<reset>

将类重置为初始状态。

=head1 SEE ALSO

L<Getopt::Long>

L<Getopt::EX>, L<Getopt::EX::Long>

=head1 AUTHOR

Kazumasa Utashiro

=head1 COPYRIGHT

The following copyright notice applies to all the files provided in
this distribution, including binary files, unless explicitly noted
otherwise.

Copyright 2021-2025 Kazumasa Utashiro

=head1 LICENSE

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
