#!/usr/bin/env perl use strict; use warnings; use autodie; use IO::Handle; our $VERSION = '0.01'; # It's not very useful to have the user type in lines to then select... exit if -t *STDIN; # We need to talk to the user directly. stdin and stdout are what we're # filtering. open my $in, '<', '/dev/tty'; open my $out, '>', '/dev/tty'; $out->autoflush(1); my @lines; my @is_selected; my $cursor = 0; my $done = 0; my $counted = 0; my %command_table; %command_table = ( j => { command => sub { ++$cursor }, documentation => "skip to the next line", }, k => { command => sub { --$cursor }, available_when => sub { $cursor > 0 }, documentation => "back up to the previous line", }, y => { command => sub { $is_selected[$cursor] = 1; ++$cursor }, documentation => "pass this line through", }, n => { command => sub { $is_selected[$cursor] = 0; ++$cursor }, documentation => "don't pass this line through", }, d => { command => sub { $done = 1 }, documentation => "print selected lines, skipping all remaining lines", }, q => { command => sub { exit }, documentation => "cancel ask, passing no lines through", }, c => { command => sub { # Force the rest of stdin, so we can get a count of how many lines # need to be decided upon push @lines, <>; $counted = 1; }, available_when => sub { !$counted }, documentation => "read the rest of input to count its size", }, '?' => { command => sub { for my $key (sort keys %command_table) { printf "%s: %s\n", $key, $command_table{$key}{documentation}; } print ": accept the current default (which is capitalized)\n"; }, documentation => "show this help", }, ); while (!$done) { my $c = get_input(); last if !defined($c); run_command($c); } # Print selected lines! print map { $lines[$_] } grep { $is_selected[$_] } 0 .. $#lines; sub get_input { # If the cursor is beyond the already-read lines, lazily read the next # line. push @lines, scalar <> until @lines > $cursor; # We hit EOF, so we're done getting input from the user. return if !defined($lines[-1]); prompt_user(); my $c = read_key(); # We always want to add a blank line before any more output, since our # prompt didn't add a newline. tell_user("\n"); return $c; } sub run_command { my $c = shift; # Space uses the default. $c = default_command() if $c eq ' '; return tell_user("Invalid response, try again!\n") if !exists($command_table{$c}); return tell_user("That command is currently unavailable.\n") if exists($command_table{$c}{available_when}) && !$command_table{$c}{available_when}->(); $command_table{$c}{command}->(); } sub read_key { # Term::ReadKey operates on STDIN. local *STDIN = $in; use Term::ReadKey; ReadMode(3); # Single-character input, without echo my $c = ReadKey; ReadMode(0); # Restore regular readline return $c; } sub tell_user { print { $out } @_; } sub prompt_user { # current line tell_user($lines[$cursor]); tell_user("Shall I pass this line through? "); my $current = $cursor + 1; # start from 1 not 0 my $max = $counted ? @lines : '?'; tell_user("($current/$max) "); my $default = default_command(); # Construct the list of available commands. my $keys = join '', # Capitalize default command map { $_ eq $default ? uc($default) : $_ } sort # Don't display help command grep { $_ ne '?' } # Hide unavailable commands grep { exists($command_table{$_}{available_when}) ? $command_table{$_}{available_when}->() : 1 } # All commands keys %command_table; tell_user("[$keys]> "); } sub default_command { $is_selected[$cursor] ? 'y' : 'n' } __END__ =head1 NAME ask - filters stdin by asking the user for each line =head1 DESCRIPTION C is a utility that filters stdin to stdout not by any automatic process, but by prompting the user about each line. =head1 AUTHOR Shawn M Moore, C =head1 PREREQUISITES autodie Term::ReadKey =pod SCRIPT CATEGORIES UNIX/System_administration =cut