File Coverage

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


line stmt bran cond sub pod time code
1             package Dist::Zilla::Chrome::Term 6.030;
2             # ABSTRACT: chrome used for terminal-based interaction
3              
4 4     4   2907 use Moose;
  4         453389  
  4         34  
5              
6 4     4   30619 use Dist::Zilla::Pragmas;
  4         13  
  4         47  
7              
8 4     4   34 use Digest::MD5 qw(md5);
  4         16  
  4         373  
9 4     4   755 use Dist::Zilla::Types qw(OneZero);
  4         12  
  4         62  
10 4     4   10934 use Encode ();
  4         15695  
  4         170  
11 4     4   722 use Log::Dispatchouli 1.102220;
  4         246610  
  4         122  
12              
13 4     4   29 use namespace::autoclean;
  4         11  
  4         50  
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   64 my $self = shift;
61 20         681 my $enc = $self->term_enc;
62              
63 20 50 33     193 if ($enc && Encode::resolve_alias($enc)) {
64 20         3489 my $layer = sprintf(":encoding(%s)", $enc);
65 3     3   23 binmode( STDOUT, $layer );
  3         7  
  3         41  
  20         376  
66 20         3901 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       1198 to_self => ($ENV{DZIL_TESTING} ? 1 : 0),
74             quiet_fatal => 'stdout',
75             });
76              
77 20   33     187343 my $use_color = $ENV{DZIL_COLOR} // -t *STDOUT;
78              
79 20 50       128 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         1144 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           my $input = $decode->($input_bytes);
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.030
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 released
231             in the last two to three years. (That is, if the most recently released
232             version is v5.40, then this module should work on both v5.40 and v5.38.)
233              
234             Although it may work on older versions of perl, no guarantee is made that the
235             minimum required version will not be increased. The version may be increased
236             for any reason, and there is no promise that patches will be accepted to lower
237             the minimum required perl.
238              
239             =head1 AUTHOR
240              
241             Ricardo SIGNES 😏 <cpan@semiotic.systems>
242              
243             =head1 COPYRIGHT AND LICENSE
244              
245             This software is copyright (c) 2023 by Ricardo SIGNES.
246              
247             This is free software; you can redistribute it and/or modify it under
248             the same terms as the Perl 5 programming language system itself.
249              
250             =cut