"Fossies" - the Fresh Open Source Software Archive 
Member "install-tl-20231127/tlpkg/TeXLive/TLConfFile.pm" (8 Dec 2021, 21507 Bytes) of package /linux/misc/install-tl-unx.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 # $Id: TLConfFile.pm 61253 2021-12-08 22:35:23Z karl $
2 # TeXLive::TLConfFile.pm - reading and writing conf files
3 # Copyright 2010-2021 Norbert Preining
4 # This file is licensed under the GNU General Public License version 2
5 # or any later version.
6
7 use strict;
8 use warnings;
9
10 package TeXLive::TLConfFile;
11
12 use TeXLive::TLUtils;
13
14 my $svnrev = '$Revision: 61253 $';
15 my $_modulerevision;
16 if ($svnrev =~ m/: ([0-9]+) /) {
17 $_modulerevision = $1;
18 } else {
19 $_modulerevision = "unknown";
20 }
21 sub module_revision {
22 return $_modulerevision;
23 }
24
25 sub new
26 {
27 my $class = shift;
28 my ($fn, $cc, $sep, $typ) = @_;
29 my $self = () ;
30 $self->{'file'} = $fn;
31 $self->{'cc'} = $cc;
32 $self->{'sep'} = $sep;
33 if (defined($typ)) {
34 if ($typ eq 'last-win' || $typ eq 'first-win' || $typ eq 'multiple') {
35 $self->{'type'} = $typ;
36 } else {
37 printf STDERR "Unknown type of conffile: $typ\n";
38 printf STDERR "Should be one of: last-win first-win multiple\n";
39 return;
40 }
41 } else {
42 # default type for backward compatibility is last-win
43 $self->{'type'} = 'last-win';
44 }
45 bless $self, $class;
46 return $self->reparse;
47 }
48
49 sub reparse
50 {
51 my $self = shift;
52 my %config = parse_config_file($self->file, $self->cc, $self->sep);
53 my $lastkey = undef;
54 my $lastkeyline = undef;
55 $self->{'keyvalue'} = ();
56 $self->{'confdata'} = \%config;
57 $self->{'changed'} = 0;
58 my $in_postcomment = 0;
59 for my $i (0..$config{'lines'}) {
60 if ($config{$i}{'type'} eq 'comment') {
61 $lastkey = undef;
62 $lastkeyline = undef;
63 $in_postcomment = 0;
64 } elsif ($config{$i}{'type'} eq 'data') {
65 $lastkey = $config{$i}{'key'};
66 $lastkeyline = $i;
67 $self->{'keyvalue'}{$lastkey}{$i}{'value'} = $config{$i}{'value'};
68 $self->{'keyvalue'}{$lastkey}{$i}{'status'} = 'unchanged';
69 if (defined($config{$i}{'postcomment'})) {
70 $in_postcomment = 1;
71 } else {
72 $in_postcomment = 0;
73 }
74 } elsif ($config{$i}{'type'} eq 'empty') {
75 $lastkey = undef;
76 $lastkeyline = undef;
77 $in_postcomment = 0;
78 } elsif ($config{$i}{'type'} eq 'continuation') {
79 if (defined($lastkey)) {
80 if (!$in_postcomment) {
81 $self->{'keyvalue'}{$lastkey}{$lastkeyline}{'value'} .=
82 $config{$i}{'value'};
83 }
84 }
85 # otherwise we are in a continuation of a comment!!! so nothing to do
86 } else {
87 print "-- UNKNOWN TYPE\n";
88 }
89 }
90 return $self;
91 }
92
93 sub file
94 {
95 my $self = shift;
96 return($self->{'file'});
97 }
98 sub cc
99 {
100 my $self = shift;
101 return($self->{'cc'});
102 }
103 sub sep
104 {
105 my $self = shift;
106 return($self->{'sep'});
107 }
108 sub type
109 {
110 my $self = shift;
111 return($self->{'type'});
112 }
113
114 sub key_present
115 {
116 my ($self, $key) = @_;
117 return defined($self->{'keyvalue'}{$key});
118 }
119
120 sub keys
121 {
122 my $self = shift;
123 return keys(%{$self->{'keyvalue'}});
124 }
125
126 sub keyvaluehash
127 {
128 my $self = shift;
129 return \%{$self->{'keyvalue'}};
130 }
131 sub confdatahash
132 {
133 my $self = shift;
134 return $self->{'confdata'};
135 }
136
137 sub by_lnr
138 {
139 # order of lines
140 # first all the line numbers >= 0,
141 # then the negative line numbers in reverse order
142 # (negative line numbers refer to new entries in the conffile)
143 # example:
144 # line number in order: 0 3 6 7 9 -1 -2 -3
145 return ($a >= 0 && $b >= 0 ? $a <=> $b : $b <=> $a);
146 }
147
148 sub value
149 {
150 my ($self, $key, $value, @restvals) = @_;
151 my $t = $self->type;
152 if (defined($value)) {
153 if (defined($self->{'keyvalue'}{$key})) {
154 my @key_lines = sort by_lnr CORE::keys %{$self->{'keyvalue'}{$key}};
155 if ($t eq 'multiple') {
156 my @newval = ( $value, @restvals );
157 my $newlen = $#newval;
158 # in case of assigning to a multiple value stuff,
159 # we assign to the first n elements, delete superficial
160 # or add new ones if necessary
161 # $value should be a reference to an array of values
162 my $listp = $self->{'keyvalue'}{$key};
163 my $oldlen = $#key_lines;
164 my $minlen = ($newlen < $oldlen ? $newlen : $oldlen);
165 for my $i (0..$minlen) {
166 if ($listp->{$key_lines[$i]}{'value'} ne $newval[$i]) {
167 $listp->{$key_lines[$i]}{'value'} = $newval[$i];
168 if ($listp->{$key_lines[$i]}{'status'} ne 'new') {
169 $listp->{$key_lines[$i]}{'status'} = 'changed';
170 }
171 $self->{'changed'} = 1;
172 }
173 }
174 if ($minlen < $oldlen) {
175 # we are assigning less values to more lines, so we have to
176 # remove the remaining ones
177 for my $i (($minlen+1)..$oldlen) {
178 $listp->{$key_lines[$i]}{'status'} = 'deleted';
179 }
180 $self->{'changed'} = 1;
181 }
182 if ($minlen < $newlen) {
183 # we have new values
184 my $ll = $key_lines[$#key_lines];
185 # if we are adding the first new entry, set line to -1,
186 # otherwise decrease the line number (already negative
187 # for new lines)
188 $ll = ($ll >= 0 ? -1 : $ll-1);
189 for my $i (($minlen+1)..$newlen) {
190 $listp->{$ll}{'status'} = 'new';
191 $listp->{$ll}{'value'} = $newval[$i];
192 $ll--;
193 }
194 $self->{'changed'} = 1;
195 }
196 } else {
197 # select element based on first-win or last-win type
198 my $ll = $key_lines[($t eq 'first-win' ? 0 : $#key_lines)];
199 #print "lastwin = $ll\n";
200 if ($self->{'keyvalue'}{$key}{$ll}{'value'} ne $value) {
201 $self->{'keyvalue'}{$key}{$ll}{'value'} = $value;
202 # as long as the key/value pair is not new,
203 # we set its status to changed
204 if ($self->{'keyvalue'}{$key}{$ll}{'status'} ne 'new') {
205 $self->{'keyvalue'}{$key}{$ll}{'status'} = 'changed';
206 }
207 $self->{'changed'} = 1;
208 }
209 }
210 } else { # all new key
211 my @newval = ( $value, @restvals );
212 my $newlen = $#newval;
213 for my $i (0..$newlen) {
214 $self->{'keyvalue'}{$key}{-($i+1)}{'value'} = $value;
215 $self->{'keyvalue'}{$key}{-($i+1)}{'status'} = 'new';
216 }
217 $self->{'changed'} = 1;
218 }
219 }
220 # $self->dump_myself();
221 if (defined($self->{'keyvalue'}{$key})) {
222 my @key_lines = sort by_lnr CORE::keys %{$self->{'keyvalue'}{$key}};
223 if ($t eq 'first-win') {
224 return $self->{'keyvalue'}{$key}{$key_lines[0]}{'value'};
225 } elsif ($t eq 'last-win') {
226 return $self->{'keyvalue'}{$key}{$key_lines[$#key_lines]}{'value'};
227 } elsif ($t eq 'multiple') {
228 return map { $self->{'keyvalue'}{$key}{$_}{'value'} } @key_lines;
229 } else {
230 die "That should not happen: wrong type: $!";
231 }
232 }
233 return;
234 }
235
236 sub delete_key
237 {
238 my ($self, $key) = @_;
239 my %config = %{$self->{'confdata'}};
240 if (defined($self->{'keyvalue'}{$key})) {
241 for my $l (CORE::keys %{$self->{'keyvalue'}{$key}}) {
242 $self->{'keyvalue'}{$key}{$l}{'status'} = 'deleted';
243 }
244 $self->{'changed'} = 1;
245 }
246 }
247
248 sub rename_key
249 {
250 my ($self, $oldkey, $newkey) = @_;
251 my %config = %{$self->{'confdata'}};
252 for my $i (0..$config{'lines'}) {
253 if (($config{$i}{'type'} eq 'data') &&
254 ($config{$i}{'key'} eq $oldkey)) {
255 $config{$i}{'key'} = $newkey;
256 $self->{'changed'} = 1;
257 }
258 }
259 if (defined($self->{'keyvalue'}{$oldkey})) {
260 $self->{'keyvalue'}{$newkey} = $self->{'keyvalue'}{$oldkey};
261 delete $self->{'keyvalue'}{$oldkey};
262 $self->{'keyvalue'}{$newkey}{'status'} = 'changed';
263 $self->{'changed'} = 1;
264 }
265 }
266
267 sub is_changed
268 {
269 my $self = shift;
270 return $self->{'changed'};
271 }
272
273 sub save
274 {
275 my $self = shift;
276 my $outarg = shift;
277 my $closeit = 0;
278 # unless $outarg is defined or we are changed, return immediately
279 return if (! ( defined($outarg) || $self->is_changed));
280 #
281 my %config = %{$self->{'confdata'}};
282 #
283 # determine where to write to
284 my $out = $outarg;
285 my $fhout;
286 if (!defined($out)) {
287 $out = $config{'file'};
288 my $dn = TeXLive::TLUtils::dirname($out);
289 TeXLive::TLUtils::mkdirhier($dn);
290 if (!open(CFG, ">$out")) {
291 tlwarn("Cannot write to $out: $!\n");
292 return 0;
293 }
294 $closeit = 1;
295 $fhout = \*CFG;
296 } else {
297 # check what we got there for $out
298 if (ref($out) eq 'SCALAR') {
299 # that is a file name
300 my $dn = TeXLive::TLUtils::dirname($out);
301 TeXLive::TLUtils::mkdirhier($dn);
302 if (!open(CFG, ">$out")) {
303 tlwarn("Cannot write to $out: $!\n");
304 return 0;
305 }
306 $fhout = \*CFG;
307 $closeit = 1;
308 } elsif (ref($out) eq 'GLOB') {
309 # that hopefully is a fh
310 $fhout = $out;
311 } else {
312 tlwarn("Unknown out argument $out\n");
313 return 0;
314 }
315 }
316
317 #
318 # first we write the config file as close as possible to orginal layout,
319 # and after that we add new key/value pairs
320 my $current_key_value_is_changed = 0;
321 for my $i (0..$config{'lines'}) {
322 if ($config{$i}{'type'} eq 'comment') {
323 print $fhout "$config{$i}{'value'}";
324 print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n");
325 } elsif ($config{$i}{'type'} eq 'empty') {
326 print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n");
327 } elsif ($config{$i}{'type'} eq 'data') {
328 $current_key_value_is_changed = 0;
329 # we have to check whether the original data has been changed!!
330 if ($self->{'keyvalue'}{$config{$i}{'key'}}{$i}{'status'} eq 'changed') {
331 $current_key_value_is_changed = 1;
332 print $fhout "$config{$i}{'key'} $config{'sep'} $self->{'keyvalue'}{$config{$i}{'key'}}{$i}{'value'}";
333 if (defined($config{$i}{'postcomment'})) {
334 print $fhout $config{$i}{'postcomment'};
335 }
336 # if a value is changed, we do not print out multiline stuff
337 # as keys are not split
338 print $fhout "\n";
339 } elsif ($self->{'keyvalue'}{$config{$i}{'key'}}{$i}{'status'} eq 'deleted') {
340 $current_key_value_is_changed = 1;
341 } else {
342 $current_key_value_is_changed = 0;
343 # the original already contains the final \, so only print new line
344 print $fhout "$config{$i}{'original'}\n";
345 }
346 } elsif ($config{$i}{'type'} eq 'continuation') {
347 if ($current_key_value_is_changed) {
348 # ignore continuation lines if values are changed
349 } else {
350 print $fhout "$config{$i}{'value'}";
351 print $fhout ($config{$i}{'multiline'} ? "\\\n" : "\n");
352 }
353 }
354 }
355 #
356 # save new keys
357 for my $k (CORE::keys %{$self->{'keyvalue'}}) {
358 for my $l (CORE::keys %{$self->{'keyvalue'}{$k}}) {
359 if ($self->{'keyvalue'}{$k}{$l}{'status'} eq 'new') {
360 print $fhout "$k $config{'sep'} $self->{'keyvalue'}{$k}{$l}{'value'}\n";
361 }
362 }
363 }
364 close $fhout if $closeit;
365 #
366 # reparse myself
367 if (!defined($outarg)) {
368 $self->reparse;
369 }
370 }
371
372
373
374
375 #
376 # parse/write config file
377 # these functions allow reading and writing of config files
378 # that consists of comments (comment char/string is the second argument)
379 # and pairs
380 # \s* key \s* SEP \s* value \s*
381 # where SEP is the third argument,
382 # and key does not contain neither white space nor SEP
383 # and value can be arbitry
384 #
385 # continuation lines are allowed
386 # Furthermore, at least the separator has to be on the same line as the key!!
387 # Continuations followed by comment lines are invalid!
388 #
389 sub parse_config_file {
390 my ($file, $cc, $sep) = @_;
391 my @data;
392 if (!open(CFG, "<$file")) {
393 @data = ();
394 } else {
395 @data = <CFG>;
396 chomp(@data);
397 close(CFG);
398 }
399
400 my %config = ();
401 $config{'file'} = $file;
402 $config{'cc'} = $cc;
403 $config{'sep'} = $sep;
404
405 my $lines = $#data;
406 my $cont_running = 0;
407 for my $l (0..$lines) {
408 $config{$l}{'original'} = $data[$l];
409 if ($cont_running) {
410 if ($data[$l] =~ m/^(.*)\\$/) {
411 $config{$l}{'type'} = 'continuation';
412 $config{$l}{'multiline'} = 1;
413 $config{$l}{'value'} = $1;
414 next;
415 } else {
416 # last line of a continuation
417 # do nothing, we will finish here
418 $config{$l}{'type'} = 'continuation';
419 $config{$l}{'value'} = $data[$l];
420 $cont_running = 0;
421 next;
422 }
423 }
424 # ignore continuation after comments, that is the behaviour the
425 # kpathsea library is using, so we follow it here
426 if ($data[$l] =~ m/$cc/) {
427 $data[$l] =~ s/\\$//;
428 }
429 # continuation line
430 if ($data[$l] =~ m/^(.*)\\$/) {
431 $cont_running = 1;
432 $config{$l}{'multiline'} = 1;
433 # remove the continuation marker so that we can do everything
434 # as normal below
435 $data[$l] =~ s/\\$//;
436 # we will continue below
437 }
438 # from now on, if $cont_running == 1, then it means that
439 # we are in the FIRST line of a multi line setting, so evaluate
440 # it accordingly to get the key if necessary
441
442 # empty lines are treated as comments
443 if ($data[$l] =~ m/^\s*$/) {
444 $config{$l}{'type'} = 'empty';
445 next;
446 }
447 if ($data[$l] =~ m/^\s*$cc/) {
448 # save the full line as is into the config hash
449 $config{$l}{'type'} = 'comment';
450 $config{$l}{'value'} = $data[$l];
451 next;
452 }
453 # mind that the .*? is making the .* NOT greedy, ie matching as few as
454 # possible. That way we can get rid of the comments at the end of lines
455 if ($data[$l] =~ m/^\s*([^\s$sep]+)\s*$sep\s*(.*?)(\s*)?($cc.*)?$/) {
456 $config{$l}{'type'} = 'data';
457 $config{$l}{'key'} = $1;
458 $config{$l}{'value'} = $2;
459 if (defined($3)) {
460 my $postcomment = $3;
461 if (defined($4)) {
462 $postcomment .= $4;
463 }
464 # check that there is actually a comment in the second part of the
465 # line. Otherwise we might add the continuation lines of that
466 # line to the value
467 if ($postcomment =~ m/$cc/) {
468 $config{$l}{'postcomment'} = $postcomment;
469 }
470 }
471 next;
472 }
473 # if we are still here, that means we cannot evaluate the config file
474 # give a BIG FAT WARNING but save the line as comment and continue
475 # anyway
476 my $userlineno = $l + 1; # one-based
477 warn("$0: WARNING: Cannot parse tlmgr config file ($cc, $sep)\n");
478 warn("$0: $file:$userlineno: treating this line as comment:\n");
479 warn(">>> $data[$l]\n");
480 $config{$l}{'type'} = 'comment';
481 $config{$l}{'value'} = $data[$l];
482 }
483 # save the number of lines in the config hash
484 $config{'lines'} = $lines;
485 #print "====DEBUG dumping config ====\n";
486 #dump_config_data(\%config);
487 #print "====DEBUG writing config ====\n";
488 #write_config_file(\%config);
489 #print "=============================\n";
490 return %config;
491 }
492
493 sub dump_myself {
494 my $self = shift;
495 print "======== DUMPING SELF =============\n";
496 dump_config_data($self->{'confdata'});
497 print "DUMPING KEY VALUES\n";
498 for my $k (CORE::keys %{$self->{'keyvalue'}}) {
499 print "key = $k\n";
500 for my $l (sort CORE::keys %{$self->{'keyvalue'}{$k}}) {
501 print " line =$l= value =", $self->{'keyvalue'}{$k}{$l}{'value'}, "= status =", $self->{'keyvalue'}{$k}{$l}{'status'}, "=\n";
502 }
503 }
504 print "=========== END DUMP ==============\n";
505 }
506
507 sub dump_config_data {
508 my $foo = shift;
509 my %config = %{$foo};
510 print "config file name: $config{'file'}\n";
511 print "config comment char: $config{'cc'}\n";
512 print "config separator: $config{'sep'}\n";
513 print "config lines: $config{'lines'}\n";
514 for my $i (0..$config{'lines'}) {
515 print "line ", $i+1, ": $config{$i}{'type'}";
516 if ($config{$i}{'type'} eq 'comment') {
517 print "\nCOMMENT = $config{$i}{'value'}\n";
518 } elsif ($config{$i}{'type'} eq 'data') {
519 print "\nKEY = $config{$i}{'key'}\nVALUE = $config{$i}{'value'}\n";
520 print "MULTLINE = ", ($config{$i}{'multiline'} ? "1" : "0"), "\n";
521 } elsif ($config{$i}{'type'} eq 'empty') {
522 print "\n";
523 # do nothing
524 } elsif ($config{$i}{'type'} eq 'continuation') {
525 print "\nVALUE = $config{$i}{'value'}\n";
526 print "MULTLINE = ", ($config{$i}{'multiline'} ? "1" : "0"), "\n";
527 } else {
528 print "-- UNKNOWN TYPE\n";
529 }
530 }
531 }
532
533 sub write_config_file {
534 my $foo = shift;
535 my %config = %{$foo};
536 for my $i (0..$config{'lines'}) {
537 if ($config{$i}{'type'} eq 'comment') {
538 print "$config{$i}{'value'}";
539 print ($config{$i}{'multiline'} ? "\\\n" : "\n");
540 } elsif ($config{$i}{'type'} eq 'data') {
541 print "$config{$i}{'key'} $config{'sep'} $config{$i}{'value'}";
542 if ($config{$i}{'multiline'}) {
543 print "\\";
544 }
545 print "\n";
546 } elsif ($config{$i}{'type'} eq 'empty') {
547 print ($config{$i}{'multiline'} ? "\\\n" : "\n");
548 } elsif ($config{$i}{'type'} eq 'continuation') {
549 print "$config{$i}{'value'}";
550 print ($config{$i}{'multiline'} ? "\\\n" : "\n");
551 } else {
552 print STDERR "-- UNKNOWN TYPE\n";
553 }
554 }
555 }
556
557
558 1;
559 __END__
560
561
562 =head1 NAME
563
564 C<TeXLive::TLConfFile> -- TeX Live generic configuration files
565
566 =head1 SYNOPSIS
567
568 use TeXLive::TLConfFile;
569
570 my $conffile = TeXLive::TLConfFile->new($file_name, $comment_char,
571 $separator, $type);
572 $conffile->file;
573 $conffile->cc;
574 $conffile->sep;
575 $conffile->type
576 $conffile->key_present($key);
577 $conffile->keys;
578 $conffile->value($key [, $value, ...]);
579 $conffile->is_changed;
580 $conffile->save;
581 $conffile->reparse;
582
583 =head1 DESCRIPTION
584
585 This module allows parsing, changing, saving of configuration files
586 of a general style. It also supports three different paradigma
587 with respect to multiple occurrences of keys: C<first-win> specifies
588 a configuration file where the first occurrence of a key specifies
589 the value, C<last-win> specifies that the last wins, and
590 C<multiple> that all keys are kept.
591
592 The configuration files (henceforth conffiles) can contain comments
593 initiated by the $comment_char defined at instantiation time.
594 Everything after a $comment_char, as well as empty lines, will be ignored.
595
596 The rest should consists of key/value pairs separated by the separator,
597 defined as well at instantiation time.
598
599 Whitespace around the separator, and before and after key and value
600 are allowed.
601
602 Comments can be on the same line as key/value pairs and are also preserved
603 over changes.
604
605 Continuation lines (i.e., lines with last character being a backslash)
606 are allowed after key/value pairs, but the key and
607 the separator has to be on the same line.
608
609 Continuations are not possible in comments, so a terminal backslash in
610 a comment will be ignored, and in fact not written out on save.
611
612 =head2 Methods
613
614 =over 4
615
616 =item B<< $conffile = TeXLive::TLConfFile->new($file_name, $comment_char, $separator [, $type]) >>
617
618 instantiates a new TLConfFile and returns the object. The file specified
619 by C<$file_name> does not have to exist, it will be created at save time.
620
621 The C<$comment_char> can actually be any regular expression, but
622 embedding grouping is a bad idea as it will break parsing.
623
624 The C<$separator> can also be any regular expression.
625
626 The C<$type>, if present, has to be one of C<last-win> (the default),
627 C<first-win>, or C<multiple>.
628
629 =item B<< $conffile->file >>
630
631 Returns the location of the configuration file. Not changeable (at the moment).
632
633 =item B<< $conffile->cc >>
634
635 Returns the comment character.
636
637 =item B<< $conffile->sep >>
638
639 Returns the separator.
640
641 =item B<< $conffile->type >>
642
643 Returns the type.
644
645 =item B<< $conffile->key_present($key) >>
646
647 Returns true (1) if the given key is present in the config file, otherwise
648 returns false (0).
649
650 =item B<< $conffile->keys >>
651
652 Returns the list of keys currently set in the config file.
653
654 =item B<< $conffile->value($key [, $value, ...]) >>
655
656 With one argument, returns the current setting of C<$key>, or undefined
657 if the key is not set. If the configuration file is of C<multiple>
658 type a list of keys ordered by occurrence in the file is returned.
659
660 With two (or more) arguments changes (or adds) the key/value pair to
661 the config file and returns the I<new> value.
662 In case of C<first-win> or C<last-win>, the respective occurrence
663 of the key is changed, and the others left intact. In this case only
664 the first C<$value> is used.
665
666 In case of C<multiple> the C<$values> are assigned to the keys in the
667 order of occurrence in the file. If extra values are present, they
668 are added. If on the contrary less values then already existing
669 keys are passed, the remaining keys are deleted.
670
671 =item B<< $conffile->rename_key($oldkey, $newkey) >>
672
673 Renames a key from C<$oldkey> to C<$newkey>. It does not automatically
674 save the new config file.
675
676 =item B<< $conffile->is_changed >>
677
678 Returns true (1) if some real change has happened in the configuration file,
679 that is a value has been changed to something different, or a new
680 setting has been added.
681
682 Note that changing a setting back to the original one will not reset
683 the changed flag.
684
685 =item B<< $conffile->save >>
686
687 Saves the config file, preserving as much structure and comments of
688 the original file as possible.
689
690 =item B<< $conffile->reparse >>
691
692 Reparses the configuration file.
693
694
695 =back
696
697 =head1 EXAMPLES
698
699 For parsing a C<texmf.cnf> file you can use
700
701 $tmfcnf = TeXLive::TLConfFile->new(".../texmf-dist/web2c", "[#%]", "=");
702
703 since the allowed comment characters for texmf.cnf files are # and %.
704 After that you can query keys:
705
706 $tmfcnf->value("TEXMFMAIN");
707 $tmfcnf->value("trie_size", 900000);
708
709 =head1 AUTHORS AND COPYRIGHT
710
711 This script and its documentation were written for the TeX Live
712 distribution (L<https://tug.org/texlive>) and both are licensed under the
713 GNU General Public License Version 2 or later.
714
715 =cut
716
717 ### Local Variables:
718 ### perl-indent-level: 2
719 ### tab-width: 2
720 ### indent-tabs-mode: nil
721 ### End:
722 # vim:set tabstop=2 expandtab: #