File Coverage

blib/lib/App/sdview/Style.pm
Criterion Covered Total %
statement 95 104 91.3
branch 24 40 60.0
condition 13 19 68.4
subroutine 14 14 100.0
pod 0 5 0.0
total 146 182 80.2


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2023 -- leonerd@leonerd.org.uk
5              
6 3     3   284352 use v5.26;
  3         13  
7 3     3   22 use warnings;
  3         13  
  3         178  
8 3     3   440 use experimental 'signatures';
  3         3327  
  3         22  
9              
10             package App::sdview::Style 0.20;
11              
12 3     3   1796 use Convert::Color;
  3         83942  
  3         197  
13 3     3   1240 use Convert::Color::XTerm 0.06;
  3         8464  
  3         10761  
14              
15             =head1 NAME
16              
17             C - store formatting style information for C
18              
19             =head1 DESCRIPTION
20              
21             This module stores formatting style information for L text output
22             formatters, such a L or
23             L.
24              
25             =head2 Config File
26              
27             =for highlighter
28              
29             Style information can be overridden by the user, supplying a
30             L-style file at F<$HOME/.sdviewrc>. Formatting for each kind of
31             paragraph is provided in a section called C, and each individual
32             key gives formatting values.
33              
34             [Para head1]
35             bold = 0|1
36             italic = 0|1
37             monospace = 0|1
38             blank_after = 0|1
39             under = NUM
40             margin = NUM
41              
42             [Para head2]
43             ...
44              
45             Specifying the special value C<~> deletes the default value for that key
46             without providing a replacement.
47              
48             The value for keys that set colours should be a string suitable for
49             L<< Convert::Color->new >>:
50              
51             [Para head1]
52             fg = vga:red
53             bg = xterm:184
54              
55             Formatting for each kind of inline format is provided in a section called
56             C, using the same key names as paragraphs.
57              
58             [Inline monospace]
59             fg = xterm:rgb(5,2,0)
60              
61             Note that the C<[Inline monospace]> style is automatically inherited by
62             C<[Para verbatim]>.
63              
64             Style information for syntax highlighting can be supplied in sections called
65             C<[Highlight $NAME]>, where each name is the F query capture name
66             for the highlight group.
67              
68             [Highlight comment]
69             bg = xterm:232
70              
71             =cut
72              
73             sub _fixup_colour_keys ( $style )
74 111     111   85189 {
  111         179  
  111         144  
75             $style->{$_} and
76 111   100     454 $style->{$_} = Convert::Color->new( $style->{$_} ) for qw( fg bg );
77             }
78              
79             my %FORMATSTYLES = (
80             bold => { bold => 1 },
81             italic => { italic => 1 },
82             monospace => { monospace => 1, bg => "xterm:235" },
83             underline => { under => 1 },
84             strikethrough => { strike => 1 },
85              
86             file => { italic => 1, under => 1 },
87             link => { under => 1, fg => "xterm:rgb(3,3,5)" }, # light blue
88             );
89             _fixup_colour_keys $_ for values %FORMATSTYLES;
90              
91 2         5 sub inline_style ( $pkg, $type )
92 2     2 0 9702 {
  2         4  
  2         3  
93 2 50       15 $FORMATSTYLES{$type} or
94             die "Unrecognised inline style for $type";
95              
96 2         36 my %style = $FORMATSTYLES{$type}->%*;
97 2   66     14 defined $style{$_} or delete $style{$_} for keys %style;
98              
99 2         10 return \%style;
100             }
101              
102 38         70 sub convert_str ( $pkg, $s )
103 38     38 0 74 {
  38         60  
  38         58  
104             return $s->clone(
105             convert_tags => {
106             ( map {
107 38         184 my $k = $_;
  266         485  
108 266 100       546 if( $k eq "link" ) {
109 38     2   207 $k => sub ($, $v) { link => $v, $FORMATSTYLES{$k}->%* };
  2         14  
  2         298  
  2         6  
  2         4  
110             }
111             else {
112 228     5   1130 $k => sub { $FORMATSTYLES{$k}->%* };
  5         584  
113             }
114             } keys %FORMATSTYLES ),
115             },
116             );
117             }
118              
119             my %PARASTYLES = (
120             head1 => { fg => "vga:yellow", bold => 1 },
121             head2 => { fg => "vga:cyan", bold => 1, margin => 2 },
122             head3 => { fg => "vga:green", bold => 1, margin => 4 },
123             head4 => { fg => "xterm:217", under => 1, margin => 5 },
124             plain => { margin => 6, blank_after => 1 },
125             verbatim => { margin => 8, blank_after => 1, inherit => "monospace" },
126             list => { margin => 6 },
127             item => { blank_after => 1 },
128             leader => { bold => 1 },
129             table => { margin => 8 },
130             "table-heading" => { bold => 1 },
131             );
132             _fixup_colour_keys $_ for values %PARASTYLES;
133              
134 51         101 sub para_style ( $pkg, $type )
135 51     51 0 198094 {
  51         96  
  51         91  
136 51 50       218 $PARASTYLES{$type} or
137             die "Unrecognised paragraph style for $type";
138              
139 51         232 my %style = $PARASTYLES{$type}->%*;
140 51 100       184 %style = ( %style, $FORMATSTYLES{delete $style{inherit}}->%* ) if defined $style{inherit};
141 51   33     261 defined $style{$_} or delete $style{$_} for keys %style;
142              
143 51         254 return \%style;
144             }
145              
146             my %HIGHLIGHTSTYLES = (
147             # Names stolen from tree-sitter's highlight theme
148             attribute => { fg => "vga:cyan", italic => 1 },
149             character => { fg => "vga:magenta" },
150             comment => { fg => "xterm:15", bg => "xterm:54", italic => 1 },
151             decorator => { fg => "xterm:140", italic => 1 },
152             function => { fg => "xterm:147", },
153             keyword => { fg => "vga:yellow", bold => 1 },
154             module => { fg => "vga:green", bold => 1 },
155             number => { fg => "vga:magenta" },
156             operator => { fg => "vga:yellow" },
157             string => { fg => "vga:magenta" },
158             type => { fg => "vga:green" },
159             variable => { fg => "vga:cyan" },
160              
161             'string.special' => { fg => "vga:red" },
162             'function.builtin' => { fg => "xterm:147", bold => 1 },
163             );
164             $HIGHLIGHTSTYLES{$_} = { fallback => "keyword" } for qw( include repeat conditional exception );
165             $HIGHLIGHTSTYLES{$_} = { fallback => "function" } for qw( method );
166             _fixup_colour_keys $_ for values %HIGHLIGHTSTYLES;
167              
168 4         7 sub highlight_style ( $pkg, $key )
169 4     4 0 4908 {
  4         8  
  4         4  
170 4         13 my @nameparts = split m/\./, $key;
171 4         13 while( @nameparts ) {
172 4 50       37 my $style = $HIGHLIGHTSTYLES{ join ".", @nameparts } or
173             pop( @nameparts ), next;
174              
175 4 100 100     23 if( keys( $style->%* ) == 1 and defined( my $fbkey = $style->{fallback} ) ) {
176 1         6 return $pkg->highlight_style( $fbkey );
177             }
178              
179 3         16 return $style;
180             }
181              
182 0         0 return undef;
183             }
184              
185             my %VALID_STYLE_KEYS = map { $_ => 1 } qw(
186             fg bg
187             bold italic monospace blank_after
188             under margin
189             );
190              
191 4         5 sub _convert_val ( $stylekey, $val )
192 4     4   6 {
  4         7  
  4         6  
193 4 100 66     21 return undef if !defined $val or $val eq "~";
194              
195 2 50       8 if( $stylekey =~ m/^(fg|bg)$/ ) {
    0          
    0          
196 2         9 return Convert::Color->new( $val );
197             }
198             elsif( $stylekey =~ m/^(bold|italic|monospace|blank_after)$/ ) {
199 0         0 return !!$val;
200             }
201             elsif( $stylekey =~ m/^(under|margin)$/ ) {
202 0         0 return 0+$val;
203             }
204             else {
205 0         0 return undef;
206             }
207             }
208              
209 1         3 sub load_config ( $pkg, $path )
210 1     1 0 1588 {
  1         2  
  1         1  
211 1         639 require Config::Tiny;
212              
213             # For unit testing, also accept a globref
214 1 50       1434 my $config = ( ref $path ) ? Config::Tiny->read_string( do { local $/; <$path> } )
  1         5  
  1         33  
215             : Config::Tiny->read( $path );
216              
217 1         168 foreach my $section ( sort keys %$config ) {
218 3         7 my $configdata = $config->{$section};
219              
220 3 100       20 if( $section =~ m/^Inline (.*)$/ ) {
    100          
    50          
221 1         3 my $format = $1;
222              
223 1 50       4 unless( $FORMATSTYLES{$format} ) {
224 0         0 warn "Unrecognised $section format in $path\n";
225 0         0 next;
226             }
227              
228 1         3 foreach my $stylekey ( sort keys $configdata->%* ) {
229 1 50       25 $VALID_STYLE_KEYS{$stylekey} or
230             warn( "Unrecognised $section key $stylekey in $path\n" ), next;
231              
232 1         4 $FORMATSTYLES{$format}{$stylekey} = _convert_val( $stylekey, $configdata->{$stylekey} );
233             }
234             }
235             elsif( $section =~ m/^Para (.*)$/ ) {
236 1         3 my $para = $1;
237              
238 1 50       13 unless( $PARASTYLES{$para} ) {
239 0         0 warn "Unrecognised $section style in $path\n";
240 0         0 next;
241             }
242              
243 1         3 foreach my $stylekey ( sort keys $configdata->%* ) {
244 1 50       3 $VALID_STYLE_KEYS{$stylekey} or
245             warn( "Unrecognised $section key $stylekey in $path\n" ), next;
246              
247 1         4 $PARASTYLES{$para}{$stylekey} = _convert_val( $stylekey, $configdata->{$stylekey} );
248             }
249             }
250             elsif( $section =~ m/^Highlight (.*)$/ ) {
251 1         4 my $keyname = $1;
252 1   50     7 my $highlight = $HIGHLIGHTSTYLES{$keyname} //= {};
253              
254 1         4 foreach my $stylekey ( sort keys $configdata->%* ) {
255 2 50       6 $VALID_STYLE_KEYS{$stylekey} or
256             warn( "Unrecognised $section key $stylekey in $path\n" ), next;
257              
258 2         7 $highlight->{$stylekey} = _convert_val( $stylekey, $configdata->{$stylekey} );
259             }
260              
261 1   66     78 defined $highlight->{$_} or delete $highlight->{$_} for keys %$highlight;
262              
263 1 50       4 if( keys( %$highlight ) > 1 ) {
264 1         3 delete $highlight->{fallback};
265             }
266             }
267             else {
268 0           warn "Unrecognised section $section in $path\n";
269             }
270             }
271             }
272              
273             =head1 AUTHOR
274              
275             Paul Evans
276              
277             =cut
278              
279             0x55AA;