"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: #