The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Module::TestConfig - Interactively prompt user to generate a config module

SYNOPSIS

  use Module::TestConfig;

  Module::TestConfig->new(
        verbose   => 1,
        defaults  => 'defaults.config',
        file      => 'MyConfig.pm',
        package   => 'MyConfig',
        order     => [ qw/defaults env/ ],
        questions => [
          [ 'Would you like tea?' => 'tea', 'y' ],
          [ 'Crumpets?' => 'crumpets', 'y' ],
        ]
  )->ask->save;

# and in another module or test file:

  use MyConfig;

  my $config = MyConfig->new;
  print $config->tea eq 'y'
    ? "We're having tea today; splendid!"
    : "No tea, I'm afraid. P'raps tomorrow.";

  print $config->crumpets eq 'y'
    ? "Crumpets; lovely!"
    : "Alas, we have no crumpets today";

DESCRIPTION

This module prompts a user for info and writes a module for later use. You can use it during the module build process (e.g. perl Makefile.PL) to share info among your test files. You can also use it to install that module into your site_perl.

Module::TestConfig writes an object-oriented file. You specify the file's location as well as the package name. When you use() the file, each of the questions' names will become an object method.

For example, if you asked the questions:

  Module::TestConfig->new(
        file      => 't/MyConfig.pm',
        package   => 'MyConfig',
        questions => [
                       [ 'Can you feel that bump?', 'feel',    'n' ],
                       [ 'Would you like another?', 'another', 'y' ],
                       { msg     => 'How many fingers am I holding up?',
                         name    => 'fingers',
                         default => 11,
                       },
                     ],
  )->ask->save;

You'd see something like this:

  Can you feel that bump? [n] y
  Would you like another? [y] n
  How many fingers am I holding up? [11]
  Module::TestConfig saved t/MyConfig.pm with these settings:
        ===================
        |    Name | Value |
        ===================
        |    feel | y     |
        | another | n     |
        | fingers | 11    |
        +---------+-------+

...and the file t/MyConfig.pm was written. To use it, add this to another file:

  use MyConfig;

  my $config = MyConfig->new;
  print $config->fingers;  # prints 11
  print $config->feel;     # prints 'y'
  print $config->another;  # prints 'n'

PUBLIC METHODS

new()

Args and defaults:

  verbose   => 1,
  defaults  => 'defaults.config',
  file      => 'MyConfig.pm',
  package   => 'MyConfig',
  order     => [ 'defaults' ],
  questions => [ ... ],

Returns: a new Module::TestConfig object.

questions()

Set up the questions that we'll ask of the user. This is a list (or array) of questions. Each question can be in one of two forms, the array form or the hash form. See Module::TestConfig::Question for more about the question's arguments.

Args: an array of question hashes (or question arrays) a list of question hashes (or question arrays)

e.g. (to keep it simple, I'll only give an example of a hash-style question):

  [
    { question => "Question to ask:",
      name     => "foo",
      default  => 42,
      noecho   => 0,
      skip     => sub { shift->answer('bar') }, # skip if bar is true
      validate => { regex => /^\d+$/ }, # answer must be all numbers
    },
    ...
  ]

Returns: a list of questions in list context an arrayref of questions in scalar context.

Here's an overview of the hash-style arguments to set up a question. See Module::TestConfig::Question for more details.

Args:

question or msg:

A string like "Have you seen my eggplant?" or "Kittens:". They look best when they end with a ':' or a '?'.

name:

A simple mnemonic used for looking up values later. Since it will turn into a method name in the future, it ought to match /\w+/.

default or def:

optional. a default answer.

noecho:

optional. 1 or 0. Do we print the user's answer while they are typing? This is useful when asking for for passwords.

skip:

optional. Accepts either a coderef or a scalar. The Module::TestConfig object will be passed to the coderef as its first and only argument. Use it to look up answer()s. If the coderef returns true or the scalar is true, the current question will be skipped.

validate:

optional. A hashref suitable for Params::Validate::validate_pos(). See Params::Validate. The question will loop over and over until the user types in a correct answer - or at least one that validates. After the user tries and fails 10 times, Module::TestConfig will give up and skip the question.

e.g.

  Module::TestConfig->new(
        questions => [ { question  => 'Choose any integer: ',
                         name      => 'num',
                         default   => 0,
                         validate  => { regex => qr/^\d+$/ },
                       },
                       { question  => 'Pick an int between 1 and 10: ',
                         name      => 'guess',
                         default   => 5,
                         validate  => {
                             callbacks => {
                                 '1 <= guess <= 10',
                                 sub { my $n = shift;
                                       return unless $n =~ /^\d+$/;
                                       return if $n < 1 || $n > 10;
                                       return 1;
                                     },
                             }
                         }
                       },
                     ]
  )->ask->save;

would behave like this when run:

  Pick a number, any integer:  [0] peach
  Your answer didn't validate.
  The 'num' parameter did not pass regex check
  Please try again. [Attempt 1]

  Pick a number, any integer:  [0] plum
  Your answer didn't validate.
  The 'num' parameter did not pass regex check
  Please try again. [Attempt 2]

  Pick a number, any integer:  [0] 5
  Pick an integer between 1 and 10:  [5] 12
  Your answer didn't validate.
  The 'guess' parameter did not pass the '1 <= guess <= 10' callback
  Please try again. [Attempt 1]

  Pick an integer between 1 and 10:  [5] -1
  Your answer didn't validate.
  The 'guess' parameter did not pass the '1 <= guess <= 10' callback
  Please try again. [Attempt 2]

  Pick an integer between 1 and 10:  [5] 3
  Module::TestConfig saved MyConfig.pm with these settings:
        =================
        |  Name | Value |
        =================
        |   num | 5     |
        | guess | 3     |
        +-------+-------+
ask()

Asks the user the questions.

Returns: a Module::TestConfig object.

save()

Writes our answers to the file. The file created will always be 0600 for security reasons. This may change in the future. See "SECURITY" for more info.

defaults()

A file parsed by Config::Auto which may be used as default answers to the questions. See "order()"

Default: "defaults.config"

Args: A filename or path.

Returns: A filename or path.

save_defaults()

Writes a new defaults file. The key-value separator should be either ':' or '=' to keep it compatible with Config::Auto. See "order()" for more about defaults.

Args and defaults:

   file => $object->defaults, # 'defaults.config'
   sep  => ':',

Returns: 1 on success, undef on failure. Any error message can be found in $!.

file()

The filename that gets written during save().

Default: "MyConfig.pm"

Args: a filepath that should probably end in ".pm". e.g. "t/MyConfig.pm"

Returns: the filename or path.

package()

The file's package name written during save().

Default: "MyConfig"

Args: a package namespace. e.g. "Foo::Bar::Baz"

Returns: the set package.

order()

Default: [ 'defaults' ]

Args: [ qw/defaults env/ ] [ qw/env defaults/ ] [ qw/defaults/ ] [ qw/env/ ] [ ] # Don't preload defaults from file or env

Where do we look up defaults for the questions? They can come from either a file, the environment or perhaps come combination of both.

defaults:

a file read by Config::Auto that must parse to a hash of question names and values.

env:

environment variables in either upper or lowercase.

The following will also be checked, in order:

answers:

Any answers already hard-set via answers().

a question's default:

A default supplied with the question.

There's a security risk accepting defaults from the environment or from a file. See "SECURITY".

answer()

Get the current value of a question. Useful when paired with a skip or validate.

Args: a question's name.

Returns: The current value or undef.

verbose()

Should the module be chatty while running? Should it print a report at the end?

Default: 1

Args: 1 or 0

Returns: current value

PROTECTED METHODS

These are documented primarily for subclassing.

answers()

A user's questions get stored here. If you preset any answers before calling ask(), they may be used as defaults. See "order()" for those rules.

Args: a hash of question names and answers

Returns: A hash in list context, a hashref in scalar context.

prompt()

Ask the user a question. It's your job to store the answer in the answers().

Args: $question, $default_answer, \%options

Returns: the user's answer, a suitable default or ''.

get_default()

Get a question's default answer from env, file, answer() or the question. It's printed with a prompt()ed question.

load_defaults()

Uses Config::Auto to parse the file specified by defaults(). Defaults are stored in $obj->{_defaults}.

package_text()

Returns the new file's text. This should take package() and answers() into account.

qi()

The current question index.

Args: A number

Returns: The current number

report()

Returns a report of question names and values. Will be printed if the object is in verbose mode. This calls report_pretty() or report_plain() depending on whether Text::AutoFormat is available. Won't print any passwords (questions called with noecho => 1 ) in plaintext.

report_pretty()

Prints the report like this:

        ==================
        |   Name | Value |
        ==================
        |    one | 1     |
        |    two | 2     |
        |  three | 3     |
        |  fruit | kiwi  |
        |   meat | pork  |
        | passwd | ***** |
        +--------+-------+
report_plain()

Prints the report like this:

        one: 1
        two: 2
        three: 3
        fruit: kiwi
        meat: pork
        passwd: *****

SECURITY

The resultant file (MyConfig.pm by default) will be chmod'd to 0600 but it will remain on the system.

The default action is to load a file named 'defaults.config' and use that for, well, defaults. If someone managed to put their own defaults.config into your working directory, they might be able to sneak a bad default past an unwitting user.

Using the environment as input may be a security risk - or a potential bug - especially if your names mimic existing environment variables.

AUTHOR

Joshua Keroes <jkeroes@eli.net>

COPYRIGHT AND LICENSE

Copyright 2003 by Joshua Keroes <jkeroes@eli.net>

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

SEE ALSO

Module::TestConfig::Question ExtUtils::MakeMaker Module::Build