App-Project-Doctor

 view release on metacpan or  search on metacpan

lib/App/Project/Doctor/Fixer.pm  view on Meta::CPAN

		? $self->_apply_all(\@fixable)
		: $self->_interactive_loop(\@fixable);
}

# ---------------------------------------------------------------------------
# Private helpers
# ---------------------------------------------------------------------------

# Purpose:    Print a numbered list of fixes to STDOUT.
# Entry:      $fixable is a non-empty arrayref of Finding objects.
# Exit:       Returns nothing; side-effect is printed output only.
# Side effects: Writes to STDOUT.
sub _print_fix_list {
	my $fixable = shift;
	print "\nSuggested fixes:\n";
	# Number each finding starting at 1 so the user can reference them by number.
	my $i = 0;
	printf "  [%d] %s\n", ++$i, $_->message for @{$fixable};
	return;
}

# Purpose:    Read the user's choice from STDIN and apply the selected fixes.
# Entry:      $fixable is a non-empty arrayref of Finding objects.
# Exit:       Integer count of fixes successfully applied.
# Side effects: Reads STDIN, writes STDOUT, may modify the filesystem via fix coderefs.
sub _interactive_loop {
	my ($self, $fixable) = @_;

	# Show the numbered list so the user knows what choices are available.
	_print_fix_list($fixable);
	print "\nWould you like me to apply them? [Y/n/1,3] ";

	# Read one line from the user; return 0 cleanly if STDIN is closed (e.g. in a pipe).
	my $answer = <STDIN>;
	return 0 unless defined $answer;
	chomp $answer;    # Remove the trailing newline before comparing.

	# Empty input or "yes" means apply everything.
	return $self->_apply_all($fixable)
		if $answer eq '' || $answer =~ /^y(?:es)?$/i;

	# Explicit "no" -- tell the user we skipped and return.
	if ($answer =~ /^n(?:o)?$/i) {
		print "No fixes applied.\n";
		return 0;
	}

	# A comma/space-separated list of numbers selects individual fixes.
	if ($answer =~ /^[\d,\s]+$/) {
		my $max = scalar @{$fixable};    # The highest valid index.
		my %seen;
		# Parse the numbers, clamp to valid range, and deduplicate.
		my @indices  = grep { $_ >= 1 && $_ <= $max && !$seen{$_}++ }
		               map  { int($_) }
		               split /[\s,]+/, $answer;
		# Convert 1-based user indices to 0-based array indices.
		my @selected = map { $fixable->[$_ - 1] } @indices;
		return $self->_apply_all(\@selected);
	}

	# Anything else is unrecognised; be explicit rather than guessing.
	print "Unrecognised input -- no fixes applied.\n";
	return 0;
}

# Purpose:    Call every fix coderef in the list and count the successes.
# Entry:      $fixable is an arrayref of Finding objects (may be empty).
# Exit:       Integer count of fixes that ran without throwing.
# Side effects: Calls fix coderefs (may create/modify files), writes to STDOUT on
#               success, calls carp for each failing fix.
sub _apply_all {
	my ($self, $fixable) = @_;
	my $count = 0;
	for my $f (@{$fixable}) {
		# Wrap the fix in eval so a single failure doesn't abort all remaining fixes.
		my $ok = eval { $f->fix->($self->context); 1 };
		if ($ok) {
			# Print confirmation so the user can see what changed.
			printf "  Applied: %s\n", $f->message;
			$count++;
		} else {
			# Report the failure but continue with the next fix.
			carp "Fix failed for '" . $f->message . "': $@";
		}
	}
	# Summary line always prints, even when count is 0.
	printf "\n%d fix(es) applied.\n", $count;
	return $count;
}

1;

__END__

=head1 NAME

App::Project::Doctor::Fixer - Interactive fix application loop

=head1 VERSION

0.02

=head1 SYNOPSIS

  use App::Project::Doctor::Fixer;

  my $fixer = App::Project::Doctor::Fixer->new(
      report  => $report,
      context => $ctx,
  );
  my $count = $fixer->run;

=head1 DESCRIPTION

Presents fixable findings from a report, reads the user's choice from STDIN
(C<Y> all, C<n> none, or C<1,3> index list), and calls each selected
finding's C<fix> coderef with the current context.

Set C<non_interactive =E<gt> 1> to apply all fixes without prompting
(C<--fix> mode).



( run in 1.897 second using v1.01-cache-2.11-cpan-7fcb06a456a )