File Coverage

blib/lib/Dist/Zilla/Chrome/Term.pm
Criterion Covered Total %
statement 34 101 33.6
branch 3 36 8.3
condition 2 25 8.0
subroutine 9 20 45.0
pod 0 3 0.0
total 48 185 25.9


line stmt bran cond sub pod time code
1             package Dist::Zilla::Chrome::Term 6.037;
2             # ABSTRACT: chrome used for terminal-based interaction
3              
4 4     4   2035 use Moose;
  4         548874  
  4         47  
5              
6 4     4   39380 use Dist::Zilla::Pragmas;
  4         10  
  4         41  
7              
8 4     4   59 use Digest::MD5 qw(md5);
  4         10  
  4         454  
9 4     4   932 use Dist::Zilla::Types qw(OneZero);
  4         38  
  4         63  
10 4     4   14204 use Encode ();
  4         12  
  4         180  
11 4     4   1153 use Log::Dispatchouli 1.102220;
  4         249020  
  4         161  
12              
13 4     4   28 use namespace::autoclean;
  4         9  
  4         44  
14              
15             #pod =head1 OVERVIEW
16             #pod
17             #pod This class provides a L<Dist::Zilla::Chrome> implementation for use in a
18             #pod terminal environment. It's the default chrome used by L<Dist::Zilla::App>.
19             #pod
20             #pod =cut
21              
22             sub _str_color {
23 0     0   0 my ($str) = @_;
24              
25 0         0 state %color_for;
26              
27             # I know, I know, this is ludicrous, but guess what? It's my Sunday and I
28             # can spend it how I want.
29 0 0 0     0 state $max = ($ENV{COLORTERM}//'') eq 'truecolor' ? 255 : 5;
30 0 0       0 state $min = $max == 255 ? 384 : 5;
31 0 0       0 state $inc = $max == 255 ? 16 : 1;
32 0 0       0 state $fmt = $max == 255 ? 'r%ug%ub%u' : 'rgb%u%u%u';
33              
34 0   0     0 return $color_for{$str} //= do {
35 0         0 my @rgb = map { $_ % $max } unpack 'CCC', md5($str);
  0         0  
36              
37 0         0 my $i = ($rgb[0] + $rgb[1] + $rgb[2]) % 3;
38 0         0 while (1) {
39 0 0       0 last if $rgb[0] + $rgb[1] + $rgb[2] >= $min;
40              
41 0         0 my $next = $i++ % 3;
42              
43 0         0 $rgb[$next] = abs($max - $rgb[$next]);
44             }
45              
46 0         0 sprintf $fmt, @rgb;
47             }
48             }
49              
50             has logger => (
51             is => 'ro',
52             isa => 'Log::Dispatchouli',
53             init_arg => undef,
54             writer => '_set_logger',
55             lazy => 1,
56             builder => '_build_logger',
57             );
58              
59             sub _build_logger {
60 20     20   40 my $self = shift;
61 20         702 my $enc = $self->term_enc;
62              
63 20 50 33     179 if ($enc && Encode::resolve_alias($enc)) {
64 20         2709 my $layer = sprintf(":encoding(%s)", $enc);
65 3     3   17 binmode( STDOUT, $layer );
  3         3  
  3         23  
  20         331  
66 20         3476 binmode( STDERR, $layer );
67             }
68              
69             my $logger = Log::Dispatchouli->new({
70             ident => 'Dist::Zilla',
71             to_stdout => 1,
72             log_pid => 0,
73 20 50       972 to_self => ($ENV{DZIL_TESTING} ? 1 : 0),
74             quiet_fatal => 'stdout',
75             });
76              
77 20   33     116393 my $use_color = $ENV{DZIL_COLOR} // -t *STDOUT;
78              
79 20 50       79 if ($use_color) {
80 0         0 my $stdout = $logger->{dispatcher}->output('stdout');
81              
82             $stdout->add_callback(sub {
83 0     0   0 require Term::ANSIColor;
84 0         0 my $message = {@_}->{message};
85 0 0       0 return $message unless $message =~ s/\A\[([^\]]+)] //;
86 0         0 my $prefix = $1;
87 0         0 return sprintf "[%s] %s",
88             Term::ANSIColor::colored([ _str_color($prefix) ], $prefix),
89             $message;
90 0         0 });
91             }
92              
93 20         833 return $logger;
94             }
95              
96             has term_ui => (
97             is => 'ro',
98             isa => 'Object',
99             lazy => 1,
100             default => sub {
101             require Term::ReadLine;
102             require Term::UI;
103             Term::ReadLine->new('dzil')
104             },
105             );
106              
107             has term_enc => (
108             is => 'ro',
109             lazy => 1,
110             default => sub {
111             require Term::Encoding;
112             return Term::Encoding::get_encoding();
113             },
114             );
115              
116             sub prompt_str {
117 0     0 0   my ($self, $prompt, $arg) = @_;
118 0   0       $arg ||= {};
119 0           my $default = $arg->{default};
120 0           my $check = $arg->{check};
121              
122 0           require Encode;
123 0           my $term_enc = $self->term_enc;
124              
125             my $encode = $term_enc
126 0     0     ? sub { Encode::encode($term_enc, shift, Encode::FB_CROAK()) }
127 0 0   0     : sub { shift };
  0            
128             my $decode = $term_enc
129 0     0     ? sub { Encode::decode($term_enc, shift, Encode::FB_CROAK()) }
130 0 0   0     : sub { shift };
  0            
131              
132 0 0         if ($arg->{noecho}) {
133 0           require Term::ReadKey;
134 0           Term::ReadKey::ReadMode('noecho');
135             }
136             my $input_bytes = $self->term_ui->get_reply(
137             prompt => $encode->($prompt),
138 0     0     allow => $check || sub { length $_[0] },
139 0 0 0       (defined $default
140             ? (default => $encode->($default))
141             : ()
142             ),
143             );
144 0 0         if ($arg->{noecho}) {
145 0           Term::ReadKey::ReadMode('normal');
146             # The \n ending user input disappears under noecho; this ensures
147             # the next output ends up on the next line.
148 0           print "\n";
149             }
150              
151 0   0       my $input = $decode->($input_bytes) // q{};
152 0           chomp $input;
153              
154 0           return $input;
155             }
156              
157             sub prompt_yn {
158 0     0 0   my ($self, $prompt, $arg) = @_;
159 0   0       $arg ||= {};
160 0           my $default = $arg->{default};
161              
162 0 0         if (! $self->_isa_tty) {
163 0 0         if (defined $default) {
164 0           return OneZero->coerce($default);
165             }
166              
167             $self->logger->log_fatal(
168 0           "want interactive input, but terminal doesn't appear interactive"
169             );
170             }
171              
172 0 0         my $input = $self->term_ui->ask_yn(
173             prompt => $prompt,
174             (defined $default ? (default => OneZero->coerce($default)) : ()),
175             );
176              
177 0           return $input;
178             }
179              
180             sub _isa_tty {
181 0   0 0     my $isa_tty = -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT));
182 0           return $isa_tty;
183             }
184              
185             sub prompt_any_key {
186 0     0 0   my ($self, $prompt) = @_;
187 0   0       $prompt ||= 'press any key to continue';
188              
189 0           my $isa_tty = $self->_isa_tty;
190              
191 0 0         if ($isa_tty) {
192 0           local $| = 1;
193 0           print $prompt;
194              
195 0           require Term::ReadKey;
196 0           Term::ReadKey::ReadMode('cbreak');
197 0           Term::ReadKey::ReadKey(0);
198 0           Term::ReadKey::ReadMode('normal');
199 0           print "\n";
200             }
201             }
202              
203             with 'Dist::Zilla::Role::Chrome';
204              
205             __PACKAGE__->meta->make_immutable;
206             1;
207              
208             __END__
209              
210             =pod
211              
212             =encoding UTF-8
213              
214             =head1 NAME
215              
216             Dist::Zilla::Chrome::Term - chrome used for terminal-based interaction
217              
218             =head1 VERSION
219              
220             version 6.037
221              
222             =head1 OVERVIEW
223              
224             This class provides a L<Dist::Zilla::Chrome> implementation for use in a
225             terminal environment. It's the default chrome used by L<Dist::Zilla::App>.
226              
227             =head1 PERL VERSION
228              
229             This module should work on any version of perl still receiving updates from
230             the Perl 5 Porters. This means it should work on any version of perl
231             released in the last two to three years. (That is, if the most recently
232             released version is v5.40, then this module should work on both v5.40 and
233             v5.38.)
234              
235             Although it may work on older versions of perl, no guarantee is made that the
236             minimum required version will not be increased. The version may be increased
237             for any reason, and there is no promise that patches will be accepted to
238             lower the minimum required perl.
239              
240             =head1 AUTHOR
241              
242             Ricardo SIGNES 😏 <cpan@semiotic.systems>
243              
244             =head1 COPYRIGHT AND LICENSE
245              
246             This software is copyright (c) 2026 by Ricardo SIGNES.
247              
248             This is free software; you can redistribute it and/or modify it under
249             the same terms as the Perl 5 programming language system itself.
250              
251             =cut