Skip to content

Commit ed46ab2

Browse files
committed
M7 Phase 6 (capstone): use v5.36 on ccfe.pl
Turn on strict + warnings + modern features on the formerly-pragma-free script, completing the de-globalisation. The Phase 1-5 work made this reachable: the mutable per-screen/config/run state now lives on lexicals and $ctx, so what stays global is genuinely program-wide -- constants, lookup tables, the search-path arrays, message strings and the signal- handler-owned $cpid/$tmpfh. Those (248 symbols) are declared in one grouped `our (...)` block so the file runs under strict. The warnings pass surfaced six real issues, all fixed behaviour-preserving: - indirect-object `new IO::Select` -> IO::Select->new (fatal under v5.36) - a nonsensical `\b*` in the action-strip regex (matches null; removed) - `$buff = <$tmpfh>` in a compound while-condition lacked defined(), so a literal "0" line would end the loop early -- wrapped in defined() - trace()'s $log_level was undef when called with no level (LOG_FNAME defaults to a path, so the block runs) -> //= 0, as before - $BFIELD_*_DESCR were undef on the headless load_form path (--dump/-k, before load_msgs) -> file-scope defaults; load_msgs still overrides - separator rule-lines did `'-' x $line_width` with $COLS==0 headless (negative repeat) -> clamp width/padding to >= 0 (same empty result) Full suite 313 green with ZERO runtime warnings across all tested paths (headless + pty TUI); `perl -c` now enforces strict/warnings on ccfe.pl in CI. Scoped out: adding ccfe.pl to the perlcritic gate -- strict already catches the important issues, and the file legitimately uses patterns (eval-string config, $$path autoviv) perlcritic would false-flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fc9b09a commit ed46ab2

1 file changed

Lines changed: 86 additions & 8 deletions

File tree

src/ccfe.pl

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# Author: Massimo Loschi <ccfedevel@gmail.com>
2121
#
2222

23+
use v5.36; # strict + warnings + modern features (M7 Phase 6 capstone)
2324
use Curses;
2425
use Sys::Hostname;
2526
use File::Basename;
@@ -54,6 +55,71 @@
5455
use CCFE::Context (); # explicit run-state container (M7 de-globalisation)
5556
use FindBin (); # to locate the program at runtime (see the path block below)
5657

58+
# M7 Phase 6 (capstone): this legacy script predates `use strict`. Its package
59+
# globals -- constants, lookup tables, the search-path arrays, message strings
60+
# and the few intentionally-global runtime vars ($cpid/$tmpfh, owned by the
61+
# signal handlers) -- are declared here so the file runs under strict. The
62+
# de-globalisation (Phases 1-5) moved the mutable per-screen/config/run state
63+
# onto lexicals and $ctx; what remains global is genuinely program-wide.
64+
## BEGIN-OUR (formatted; scalars, then arrays, then hashes)
65+
our (
66+
$ALL_FIELDS_IDS_TAG, $ASKS_FIELD_PAD, $ASKS_FIELD_SIZE, $ASKS_WIN_COLS,
67+
$ASKS_WIN_FTR_ROWS, $ASKS_WIN_ROWS, $BAD_SHELL_MSG, $BAD_SHELL_TITLE,
68+
$BFIELD_DEFAULT, $BFIELD_NO, $BFIELD_NO_DESCR, $BFIELD_NULL,
69+
$BFIELD_NULL_DESCR, $BFIELD_YES, $BFIELD_YES_DESCR, $BIG_OUTPUT_MSG,
70+
$BIG_OUTPUT_TITLE, $BINDIR, $BOOLEAN, $BOOLEAN_FIELD_SIZE, $CALLNAME,
71+
$CALL_SHELL_MSG, $CALL_SYS_ES_MSG, $CALL_SYS_MSG, $CONFIRM_DESCR_NO,
72+
$CONFIRM_DESCR_YES, $CONFIRM_TITLE, $CURSES_ACTIVE, $DEBUG, $DESCR,
73+
$DMENU_DEF_FNAME, $EMPTY_LIST_MSG, $EMPTY_LIST_TITLE,
74+
$ERR_EMPTY_FIELD_MSG, $ERR_EMPTY_FIELD_TITLE, $ERR_LOAD_INITIAL_OBJ,
75+
$ES_CANCEL, $ES_EXIT, $ES_FOPEN_ERR, $ES_FOPEN_ERR_MSG, $ES_NOT_FOUND,
76+
$ES_NOT_FOUND_MSG, $ES_NO_ERR, $ES_NO_ERR_MSG, $ES_NO_ITEMS,
77+
$ES_NO_ITEMS_MSG, $ES_SYNTAX_ERR, $ES_SYNTAX_ERR_MSG, $ES_USER_REQ,
78+
$ETCDIR, $FALSE, $FIELD_LMARGIN, $FIELD_RMARGIN, $FIELD_VALUE_GAP,
79+
$FORMEXT, $FORM_ARGV_ID, $FORM_ERR_TITLE, $FOUND_NONE_MSG,
80+
$FOUND_NONE_TITLE, $FSEP_ID_PRFX, $FS_BOTTOM_ROWS, $FS_HEADER_ROWS,
81+
$FS_TOP_ROWS, $HAS_COLOR, $HOSTNAME, $HTAB_COLS, $INIT_DISABLE_FIELDS,
82+
$INIT_ENABLE_FIELDS, $INIT_FORM_ERR_MSG, $INIT_REMOVE_FIELDS,
83+
$KEY_ENTER_LABEL, $KEY_F10_LABEL, $KEY_F1_LABEL, $KEY_F2_LABEL,
84+
$KEY_F3_LABEL, $KEY_F4_LABEL, $KEY_F5_LABEL, $KEY_F6_LABEL, $KEY_F7_LABEL,
85+
$KEY_F8_LABEL, $KEY_F9_LABEL, $KEY_FIND_LABEL, $KEY_FNEXT_LABEL,
86+
$KEY_INTR_LABEL, $KEY_SELALL_LABEL, $KEY_UNSELALL_LABEL, $LANG_ID,
87+
$LEGACY_DIR, $LIBDIR, $LIST_CMD_ERR_MSG, $LIST_CMD_ERR_TITLE,
88+
$LOAD_FORM_ERR_MSG, $LOAD_MENU_ERR_MSG, $LOGDIR, $LOG_ACTION_CMD,
89+
$LOG_ACTION_OUT, $LOG_DATE, $LOG_DEFAULT_CMD, $LOG_FIELDS_VAL,
90+
$LOG_INITFORM_OUT, $LOG_LIST_CMD, $LOG_MENU_CHOICE, $LOG_NORMAL,
91+
$LOG_REQUESTED, $LOG_SCAN_PATHS, $LOG_SYSCALL_ENV, $LOG_WRITE_ERROR_MSG,
92+
$LOG_WRITE_ERROR_TITLE, $LW_COLS, $LW_FOOTER_ROWS, $LW_PAD_COLS, $LW_ROW0,
93+
$MAIN_PATH, $MARK_PRIV_SHCUTS, $MENUEXT, $MENU_ERR_TITLE,
94+
$MIN_ITEMS_FOR_FIND, $MOUSE_ON, $MSGDIR, $MSG_WIN_BMSG, $MSG_WIN_ROWS,
95+
$MSG_WIN_TITLE, $MS_BOTTOM_ROWS, $MS_HEADER_ROWS, $MS_TOP_ROWS, $NO,
96+
$NORMAL, $NULLBOOLEAN, $NULL_FACTION_MSG, $NULL_FACTION_TITLE,
97+
$NULL_LIST_MSG, $NULL_LIST_TITLE, $NUMERIC, $OBJDIR, $OFF, $ON, $PERS_DIR,
98+
$PERS_WRITE_ERROR_MSG, $PERS_WRITE_ERROR_TITLE, $PREFIX, $PRIV_DIR,
99+
$RB_FAILED_MSG, $RB_LINES_MSG, $RB_OK_MSG, $RB_RUNNING_MSG, $RB_TIME_MSG,
100+
$RB_TITLE, $REALNAME, $RESTRICTED_MSG, $RESTRICTED_TITLE, $RS_BOTTOM_ROWS,
101+
$RS_HEADER_ROWS, $RS_INFO_ID, $RS_STDERR_ID, $RS_STDOUT_ID, $RS_TOP_ROWS,
102+
$SAVE_DETAILED, $SAVE_DETAILED_DESCR, $SAVE_ERROR_MSG, $SAVE_ERROR_TITLE,
103+
$SAVE_FIELDVAL_MSG, $SAVE_FIELDVAL_TITLE, $SAVE_FNAME_PROMPT,
104+
$SAVE_FNAME_TITLE, $SAVE_SCRIPT, $SAVE_SCRIPT_DESCR, $SAVE_SIMPLE,
105+
$SAVE_SIMPLE_DESCR, $SAVE_TYPE_TITLE, $SEARCH_PTRN_PROMPT,
106+
$SEARCH_PTRN_TITLE, $SEPARATOR, $SEP_LINE, $SEP_LINE_DOUBLE, $SEP_TEXT,
107+
$SEP_TEXT_CENTER, $SHOW_ACTION_TITLE, $SIMPLE, $SR_BUFF_SIZE, $STRING,
108+
$THEMEDIR, $TRUE, $UCSTRING, $USERNAME, $USR_CFG, $USR_OBJ, $VERSION,
109+
$VERSION_DATE, $VERSION_YEAR, $WAIT_MSG_MSG, $WRKDIR, $YES, $attrk,
110+
$attrv, $called_form, $ch, $choice, $cpid, $descr, $es, $exec_hh,
111+
$exec_mm, $exec_ss, $i, $id, $lflags_size, $mlmargin, $mwin, $mwinr, $opt,
112+
$out, $ovl_mode, $p, $pad_lines, $path, $pid, $prev_wdir, $res,
113+
$rflags_size, $s, $scan, $search_string, $shcut_type, $text, $tmpfh,
114+
$twin,
115+
@CONFIRM_ITEMS, @ERR_LITTLE_SCREEN, @ERR_WRONG_FPATH, @FORM_TOP_MSG,
116+
@FSKeys, @LW_DISPLAY_TOP_MSG, @LW_MULTIVAL_TOP_MSG, @LW_SINGLEVAL_TOP_MSG,
117+
@MENU_TOP_MSG, @MSKeys, @RSKeys, @cnf_path, @es_str, @flist,
118+
@fn_key_functions, @lines, @mf_path,
119+
%bool_vals, %layout_vals, %options, %sep_type_vals, %type_vals,
120+
);
121+
## END-OUR
122+
57123
# Optional display-width support. In a UTF-8 locale a label/title can occupy
58124
# fewer screen columns than it has bytes (e.g. "caf\xc3\xa9" is 5 bytes, 4
59125
# columns) and a CJK glyph occupies two columns; ncursesw already lays the
@@ -179,6 +245,11 @@
179245
$BFIELD_NO = 'NO';
180246
$BFIELD_NULL = '';
181247
$BFIELD_DEFAULT = $BFIELD_NO;
248+
# Defaults so a headless load_form (e.g. --dump/-k, before load_msgs) does not
249+
# read these undef; load_msgs overrides them with the localised descriptions.
250+
$BFIELD_YES_DESCR = 'Yes';
251+
$BFIELD_NO_DESCR = 'No';
252+
$BFIELD_NULL_DESCR = 'Not set';
182253
$MIN_ITEMS_FOR_FIND = 5;
183254

184255
$INIT_REMOVE_FIELDS = 'CCFE_REMOVE_FIELDS';
@@ -422,6 +493,7 @@ sub harden_child_env {
422493

423494
sub trace {
424495
my ( $msg, $log_level ) = @_;
496+
$log_level //= 0; # no level given -> 0 (logged only under DEBUG, as before)
425497
my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
426498
my @buff = localtime(time);
427499
my $now = '';
@@ -767,7 +839,7 @@ sub init_footer {
767839
# chgat() takes the colour pair as a separate argument, so extract
768840
# it from $ka (0 = no colour, i.e. the monochrome default).
769841
chgat( $win, $y, $x, length( $ctx->{cfg}{keys}{ $keysList[$i] }{key} ),
770-
$ka, PAIR_NUMBER($ka), NULL );
842+
$ka, PAIR_NUMBER($ka), 0 ); # 0 = NULL opts pointer
771843
}
772844
$x += $labelSize;
773845
}
@@ -846,7 +918,7 @@ sub disp_msg {
846918
addstr( $win, 1, 2 + int( ( $width - disp_width($msg) ) / 2 ), $msg );
847919
addstr( $win, 3, 2 + int( ( $width - disp_width($MSG_WIN_BMSG) ) / 2 ),
848920
$MSG_WIN_BMSG );
849-
chgat( $win, 3, 1, $width + 2, $bottom_attr, NULL, NULL );
921+
chgat( $win, 3, 1, $width + 2, $bottom_attr, 0, 0 );
850922
refresh($win);
851923
$ch = getch($win);
852924
del_panel($panel);
@@ -1071,7 +1143,7 @@ sub disp_page {
10711143
$ctx->{cfg}{SHOW_SCREEN_NAME} ? $screen_name : '',
10721144
$ovl_flag, $pos );
10731145
addstr( $win, 0, $COLS - length($buff), $buff );
1074-
chgat( $win, 0, $COLS - length($pos) - 4, 3, A_REVERSE, NULL, NULL )
1146+
chgat( $win, 0, $COLS - length($pos) - 4, 3, A_REVERSE, 0, 0 )
10751147
if $ovl_flag and $ctx->{cfg}{LAYOUT} == $NORMAL;
10761148
move( $win, $saveY, $saveX );
10771149
}
@@ -1202,13 +1274,18 @@ sub load_form {
12021274
# Separator label formatting depends on $COLS, so the pure
12031275
# parser defers it to here: centre the text, or draw a rule
12041276
# line across the field area.
1277+
# Clamp to >= 0: headless paths (--dump/-k) run before
1278+
# initscr, so $COLS is 0 and the width goes negative; a
1279+
# negative `x` repeat is an empty string either way.
12051280
my $line_width = $COLS - $FIELD_LMARGIN - $FIELD_RMARGIN;
1281+
$line_width = 0 if $line_width < 0;
12061282
foreach my $f ( @{ $form{fields} } ) {
12071283
next unless ( $f->{type} // 0 ) == $SEPARATOR;
12081284
next unless defined $f->{sep_type};
12091285
if ( $f->{sep_type} == $SEP_TEXT_CENTER ) {
12101286
my $lblanks =
12111287
( $line_width - length( $f->{label} ) ) / 2;
1288+
$lblanks = 0 if $lblanks < 0;
12121289
$f->{label} =
12131290
sprintf( "%s%s", ' ' x ($lblanks), $f->{label} );
12141291
}
@@ -3920,7 +3997,7 @@ sub do_form {
39203997
elsif ( $ch == $ctx->{cfg}{keys}{show_action}{code} ) {
39213998
$sync_fields_val->();
39223999
my $args = $form{action};
3923-
$args =~ s/^(\b*[a-zA-Z]+)\(?([a-zA-Z_,]*)\)?/$1/;
4000+
$args =~ s/^([a-zA-Z]+)\(?([a-zA-Z_,]*)\)?/$1/;
39244001
if ($args) {
39254002
$prepare_action->( \$args );
39264003
my @cmd = split /\n/, $args;
@@ -4117,11 +4194,11 @@ sub run_browse {
41174194
if ( $pos0 >= 0 ) {
41184195
$nfound++;
41194196
chgat( $p, $row, $pos0, length($search_string), A_REVERSE,
4120-
NULL, NULL );
4197+
0, 0 );
41214198
if ( $pos0 + length($search_string) >= $COLS ) {
41224199
chgat( $p, $row + 1, 0,
41234200
$pos0 + length($search_string) - $COLS,
4124-
A_REVERSE, NULL, NULL );
4201+
A_REVERSE, 0, 0 );
41254202
}
41264203
}
41274204
} while ( $pos0 >= 0 );
@@ -4137,7 +4214,8 @@ sub run_browse {
41374214

41384215
move( $p, 0, 0 );
41394216
seek( $tmpfh, 0, 0 );
4140-
while ( $buff = <$tmpfh> and $c <= $ctx->{state}{pad_lines} ) {
4217+
while ( defined( $buff = <$tmpfh> ) and $c <= $ctx->{state}{pad_lines} )
4218+
{
41414219
$c++;
41424220
( $src, $buff ) = split /:/, $buff, 2;
41434221
if ( length($buff) == $COLS + 1 ) {
@@ -4214,7 +4292,7 @@ sub run_browse {
42144292
eval { $cpid = open3( $infh, $outfh, $errfh, $ctx->{cfg}{OPEN3_SHELL}, '-c', $cmd ); };
42154293
fatal($@) if $@;
42164294
trace("successfully forked child PID $cpid");
4217-
$sel = new IO::Select;
4295+
$sel = IO::Select->new;
42184296
$sel->add( $outfh, $errfh );
42194297

42204298
$tmpfh = tempfile( 'ccfeXXXXX', DIR => '/tmp' );

0 commit comments

Comments
 (0)