NAME

TUI::Handy - Text-based user interface (ANSI-only form toolkit)

VERSION

Version 0.01

SYNOPSIS

As a module:

use TUI::Handy;

my $dsl = <<'FORM';
Customer registration
Company: [________________________]
Contact: [______________]
Qty:     [###]
Price:   [$$$$$$$$]
Date:    [YYYYMMDD]
--------------------------------
[X] Shipped
[ ] Stock check

Payment:
(*) Cash
( ) Transfer
( ) Credit
--------------------------------
[Register]
[Quit]
FORM

my $tui = TUI::Handy->new(dsl => $dsl);
$tui->set('Company',  'ACME');                 # preset a value
$tui->set('Register', sub { my $form = shift; do_save($form); 0 });
$tui->set('Quit',     sub { 1 });              # true closes the form
my $form = $tui->run;
print "Company = $form->{'Company'}\n";

As a command:

perl Handy.pm form.txt

DESCRIPTION

TUI::Handy renders a plain-text form definition as an interactive console form, using nothing but ANSI escape sequences. It is pure Perl, has no dependencies at all beyond strict and vars, and runs on Perl 5.005_03 and later.

The form definition is not a description of a screen; it is the screen. The text you write is drawn as written, and the labels you write become the keys of the hash that run() returns, so the layout and the data structure cannot drift apart. Moving a field one line up is an edit to the text, not to any code.

Why this module exists

The Perl ecosystem already offers several ways to build a terminal interface. Every one of them is unavailable in the environments this module targets:

  • Curses and Curses::UI need an XS build and the ncurses library. A locked-down server with no compiler, or no ncurses headers, cannot install them.

  • Prima and Tk are graphical toolkits and assume a display.

  • Term::Choose, Term::Menus and friends are pure Perl but present menus and selection lists; they are not form editors.

  • Term::ReadLine::Gnu needs XS and the GNU readline library.

TUI::Handy therefore does not compete with Curses::UI; it fills the gap below it. The intended situation is a machine where CPAN is unreachable, no compiler is installed and the perl is whatever shipped with the system: a plant or in-house server on a closed network, a customer site, a locked image, a classroom. Installing TUI::Handy there means copying one file to TUI/Handy.pm somewhere in @INC. Nothing is built, nothing is downloaded, and the same file works on a perl that has not been updated for twenty years.

The second reason is multibyte text. Drawing a form that contains Japanese requires knowing the display width of every character, and Encode is core only from Perl 5.8. TUI::Handy computes width and character boundaries at the byte level for UTF-8, Shift_JIS (CP932) and EUC-JP, so a Japanese form stays aligned without loading anything. The module source itself is US-ASCII; every non-ASCII byte lives in the form definition, which is read at run time.

THE FORM DSL

The DSL has no attributes and no syntax beyond what is drawn.

Heading / separator

Any plain line, drawn as written. A heading line immediately above a run of radio buttons also names their group.

Text box

Label: [____]. The characters between the brackets set both the field width and the field type: _ half-width text, a full-width square full-width text, # numeric (right aligned), $ or \ currency (right aligned), YYYYMMDD or % a date. Input that does not fit the type is rejected as it is typed, and text longer than the field is clipped.

Checkbox

[X] Label / [ ] Label. Stored as 1 or 0 under Label.

Radio button

(*) Label / ( ) Label. Grouped by the preceding heading line, and stored under that group title as the label of the selected member.

Button

[Label] alone on its line. Its value is a code reference registered with set(); returning true from the handler closes the form.

Keys

TAB and Down move to the next field, Shift-TAB and Up to the previous one. Enter, Space and the arrow keys edit the current field or activate a button. ESC aborts the form.

METHODS

TUI::Handy->new(dsl => $text)
TUI::Handy->new(file => $path)

Parse a form definition and return an object. dsl takes the definition as a string, file reads it from a file.

$tui->set($key, $value)

Preset a field, or register a button handler. A code reference is taken as a handler for the button named $key; anything else is a value for the text box, checkbox or radio group named $key. Returns the object, so calls chain.

$tui->run

Run the form and return the value hash reference when it closes. Keys are the labels from the definition. Button entries hold the registered code reference.

$tui->form

The same hash reference, live, at any time. Useful inside a button handler, which is passed it as its first argument.

$tui->pressed

The label of the button that closed the form, or undef if the form was closed some other way (for instance by ESC).

$tui->close_form

Ask the run loop to finish. Intended for a button handler that needs to do something else before the form closes.

A button handler is called with the value hash reference and the object. Returning true closes the form; returning false leaves it open, which is how a handler rejects what the user entered without losing any of it. pressed() is set only by a press that actually closed the form, so a form abandoned after a rejected press reports undef. Both drivers follow this rule.

ENVIRONMENT

TUI_HANDY_ENCODING

utf8 (the default off Windows), sjis (the default on Windows) or euc. It is read once, as the module is loaded, so it has to be set before use TUI::Handy. Later on, assign to $TUI::Handy::ENCODING instead.

TUI_HANDY_MODE

Force ansi or line instead of auto-detecting. It is read by run(), so it may be set at any point before the form is run.

EXAMPLES

The eg/ directory of the distribution holds runnable examples:

eg/quickstart.pl

The smallest useful form: three fields and two buttons.

eg/setup_wizard.pl

A configuration wizard. Reads an existing key=value file into the form, lets the user edit it and writes it back.

eg/master_entry.pl

Repeated master-record entry. Validates each record, appends it to a tab-separated file and re-opens the form for the next one.

LIMITATIONS

These are deliberate, and they are the price of the dependency-free design. If you need what is listed here, Curses::UI is the right tool.

  • One screen. There is no scrolling, so a form has to fit the terminal.

  • Form widgets only: no lists, tables, menus, tabs or sub-windows.

  • No colour beyond reverse video for the focused widget, and no mouse support.

  • A terminal resize during the run is not tracked.

  • Display width is decided from the encoding of each character rather than from a Unicode table, because no table can be loaded. ASCII, the Japanese full-width ranges and half-width katakana -- the characters these forms are made of -- come out right in all three encodings. Under UTF-8, though, everything else above U+07FF is counted as two columns, so a form whose labels use narrow characters from that range (dashes and quotation marks from General Punctuation, arrows, box-drawing) will draw a little wide. Shift_JIS and EUC-JP cannot express those characters at all, so this applies to UTF-8 only.

  • One key per label. A label written twice yields two widgets sharing one hash key, so the second overwrites the first and set() reaches only the first. A button whose label repeats the label of a field is the same collision, and there the button ends up with no entry of its own. Labels within a form need to be distinct.

  • In line mode an empty answer means "keep what is there", which is what makes a second pass over the fields cheap. The cost is that a value already entered cannot be cleared from that driver.

  • Full-screen ANSI mode needs a terminal that stty can place in cbreak mode, which covers Linux, the other Unices, macOS, WSL, Cygwin and Git Bash. On a bare Windows cmd.exe, where stty is absent and no external module may be used, TUI::Handy falls back to a portable line-oriented driver. The display is plainer -- one prompt per line instead of a screen -- but the same definition yields the same hash, and button handlers behave identically: a handler that returns false keeps the form open there too, and the fields are then walked again with everything already entered offered as the defaults.

SEE ALSO

Curses::UI for a full widget toolkit where XS and ncurses are available; Term::Choose and Term::Menus for selection lists; Prima and Tk for graphical interfaces.

AUTHOR

INABA Hitoshi <ina.cpan@gmail.com> in a CPAN

LICENSE AND COPYRIGHT

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

This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.