# autolatex - Config.pm # Copyright (C) 1998-15 Stephane Galland # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. =pod =head1 NAME Config.pm - Configuration Files =head1 DESCRIPTION Provides a set of utilities for manipulating cofiguration files. To use this library, type C. =head1 FUNCTIONS The provided functions are: =over 4 =cut package AutoLaTeX::Core::Config; $VERSION = '36.0'; @ISA = ('Exporter'); @EXPORT = qw( &getProjectConfigFilename &getUserConfigFilename &getSystemConfigFilename &getSystemISTFilename &readConfiguration &readConfigFile &getUserConfigDirectory &cfgBoolean &doConfigurationFileFixing &cfgToBoolean &writeConfigFile &readOnlySystemConfiguration &readOnlyUserConfiguration &readOnlyProjectConfiguration &setInclusionFlags &reinitInclusionFlags &cfgIsBoolean &rebuiltConfigValue ) ; @EXPORT_OK = qw(); require 5.014; use strict; use utf8; use warnings; use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION); use File::Spec; use Config::Simple; use AutoLaTeX::Core::OS; use AutoLaTeX::Core::Util; use AutoLaTeX::Core::IntUtils; # Constant that is representing an empty string in the INI file. # This constant is mandatory because Config::Simple does not # support the empty values, such as "KEY =". In place, # the INI file must contains something like "KEY = <<<>>>". use constant EMPTY_INI_VALUE => '<<<>>>'; ####################################################### # Comments for the sections of the configuration file my %SECTION_COMMENTS = ( 'viewer' => _T("Configuration of the viewing functions"), 'generation' => _T("Configuration of the generation functions"), 'clean' => _T("Configuration of the several cleaning functions"), 'scm' => _T("Configuration of the SCM functions"), 'gtk' => _T("GTK interface configuration"), 'qt' => _T("Qt interface configuration"), 'windows' => _T("Windows interface configuration"), 'macos' => _T("MacOS interface configuration"), 'wxwidget' => _T("wxWidgets interface configuration"), ); ####################################################### # Comments for the public configuration entries my %CONFIGURATION_COMMENTS = ( # # CLEAN 'clean.files to clean' => _T( "List of additional files to remove when cleaning (shell ". "wild cards are allowed). This list is used when the ". "target 'clean' is invoked."), 'clean.files to desintegrate' => _T( "List of additional files to remove when all cleaning ". "(shell wild cards are allowed). This list is used when ". "the target 'cleanall' is invoked."), # GENERATION 'generation.biblio' => _T( "Indicates if bibliography tool (bibtex,biber) should be run ('yes' or 'no')."), 'generation.generate images' => _T( "Does the figures must be automatically generated ('yes' or 'no')?"), 'generation.image directory' => _T( "Specify the directories inside which AutoLaTeX ". "will find the pictures which must be processed ". "by the translators. Each time this option is ". "put on the command line, a directory is added ". "inside the list of the directories to explore. ". "The different paths are separated by the ". "path-separator character (':' on Unix, ';' on ". "Windows)"), 'generation.main file' => _T( "Main filename (this option is only available in project's ". "configuration files)."), 'generation.generation type' => _T( "Type of generation.\n pdf : use pdflatex to create a ". "PDF document."), 'generation.makeglossaries' => _T( "Indicates if glossary tool (makeglossaries) should be run ('yes' or 'no')."), 'generation.makeindex style' => _T( "Specify the style that must be used by makeindex.\n". "Valid values are:\n if a filename ". "was specified, AutoLaTeX assumes that it is the .ist ". "file;\n \@system AutoLaTeX uses the system ". "default .ist file (in AutoLaTeX distribution);\n". " \@detect AutoLaTeX will tries to find a .ist ". "file in the project's directory. If none was found, ". "AutoLaTeX will not pass a style to makeindex;\n \@none". " AutoLaTeX assumes that no .ist file must be ". "passed to makeindex;\n AutoLaTeX assumes ". "that no .ist file must be passed to makeindex."), 'generation.post compilation runs' => _T( "Defines the minimal number of times the LaTeX compilation tools (usually ". "pdflatex) is run during the last running stage of AutoLaTeX. The default value is 1."), 'generation.translator include path' => _T( "Defines the paths from which the translators could be ". "loaded. This is a list of paths separated by the path ". "separator character used by your operating system: ':' ". "on Unix platforms or ';' on Windows platforms for example."), 'generation.synctex' => _T( "Indicates if the PDF document must be produced with the SyncTeX flag on or not. ". "SyncTeX enables to link a PDF viewer (as evince) and a text editor (as Gedit). ". "When you click inside one, the other is highlighting the line in its side."), # SCM 'scm.scm commit' => _T( "Tool to launch when a SCM commit action is requested. ". "Basically the SCM tools are CVS, SVN, or GIT."), 'scm.scm update' => _T( "Tool to launch when a SCM update action is requested. ". "Basically the SCM tools are CVS, SVN, or GIT."), # VIEWER 'viewer.view' => _T( "Indicates if a viewer should be launch after the compilation. ". "Valid values are 'yes' and 'no'."), 'viewer.viewer' => _T( "Specify, if not commented,the command line of the viewer."), 'viewer.asynchronous run' => _T( "Indicates if the viewer is launched in background, or not."), ); ####################################################### # Comments for the private configuration entries # action.create config file # action.create ist file # action.fix config file # config.command line # input.project directory # input.latex file # output.directory # ouput.ist file # output.latex basename =pod =item B Replies the name of a project's configuration file which is located inside the given directory. I =over 8 =item * the components of the paths, each parameter is a directory in the path. =back I the configuration filename according to the current operating system rules. =cut sub getProjectConfigFilename(@) { my $operatingsystem = getOperatingSystem(); if (("$operatingsystem" eq 'Unix')||(("$operatingsystem" eq 'Cygwin'))) { return File::Spec->rel2abs(File::Spec->catfile(@_,".autolatex_project.cfg")); } else { return File::Spec->rel2abs(File::Spec->catfile(@_,"autolatex_project.cfg")); } } =pod =item B Replies the name of a user's configuration file. I the configuration filename according to the current operating system rules. =cut sub getUserConfigFilename() { my $confdir = getUserConfigDirectory(); if (-d "$confdir") { return File::Spec->catfile("$confdir","autolatex.conf"); } my $operatingsystem = getOperatingSystem(); if (("$operatingsystem" eq 'Unix')||(("$operatingsystem" eq 'Cygwin'))) { return File::Spec->rel2abs(File::Spec->catfile($ENV{'HOME'},".autolatex")); } elsif ("$operatingsystem" eq 'Win32') { return File::Spec->rel2abs(File::Spec->catfile("C:","Documents and Settings",$ENV{'USER'},"Local Settings","Application Data","autolatex.conf")); } else { return File::Spec->rel2abs(File::Spec->catfile($ENV{'HOME'},"autolatex.conf")); } } =pod =item B Replies the name of a user's configuration directory. I the configuration directory according to the current operating system rules. =cut sub getUserConfigDirectory() { my $operatingsystem = getOperatingSystem(); if (("$operatingsystem" eq 'Unix')||(("$operatingsystem" eq 'Cygwin'))) { return File::Spec->rel2abs(File::Spec->catfile($ENV{'HOME'},".autolatex")); } elsif ("$operatingsystem" eq 'Win32') { return File::Spec->rel2abs(File::Spec->catfile("C:","Documents and Settings",$ENV{'USER'},"Local Settings","Application Data","autolatex")); } else { return File::Spec->rel2abs(File::Spec->catfile($ENV{'HOME'},"autolatex")); } } =pod =item B Replies the name of the configuration file for all users. I the configuration filename according to the current operating system rules. =cut sub getSystemConfigFilename() { return File::Spec->catfile(getAutoLaTeXDir(),"default.cfg"); } =pod =item B Replies the name of the MakeIndex style file for all users. I the filename according to the current operating system rules. =cut sub getSystemISTFilename() { return File::Spec->catfile(getAutoLaTeXDir(),"default.ist"); } =pod =item B Replies the Perl boolean value that corresponds to the specified string. If the first parameter is not a valid boolean string, the second parameter will be replied if it is specified; if not undef will be replied; The valid string are (case insensitive): S, S, S, S. I =over 8 =item * the value to test. =item * the data structure to fill =back I nothing =cut sub cfgBoolean($;$) { if ($_[0]) { my $v = lc($_[0]); return 1 if (($v eq 'yes')||($v eq 'true')); return 0 if (($v eq 'no')||($v eq 'false')); } return $_[1]; } =pod =item B Replies the configuration's file boolean value that corresponds to the specified Perl boolean value. I =over 8 =item * the value to test. =back I nothing =cut sub cfgToBoolean($) { return ($_[0]) ? 'true' : 'false'; } =pod =item B Replies if the specified string is a valid boolean string. I =over 8 =item * the value to test. =back I nothing =cut sub cfgIsBoolean($) { if (defined($_[0])) { my $v = lc($_[0]); return 1 if (($v eq 'yes')||($v eq 'no')||($v eq 'true')||($v eq 'false')); } return 0; } =pod =item B Replies the current configuration. The configuration is extracted from the system configuration file (from AutoLaTeX distribution) and from the user configuration file. I a hashtable containing (attribute name, attribute value) pairs. The attribute name could be S to describe the attribute inside a section. =cut sub readConfiguration() { my %configuration = (); my $systemFile = getSystemConfigFilename(); my $userFile = getUserConfigFilename(); readConfigFile("$systemFile",\%configuration); readConfigFile("$userFile",\%configuration); # Remove the main intput filename if (exists $configuration{'generation.main file'}) { delete $configuration{'generation.main file'}; } return %configuration; } =pod =item B Replies the current configuration. The configuration is extracted from the system configuration file (from AutoLaTeX distribution) only. I a hashtable containing (attribute name, attribute value) pairs. The attribute name could be S to describe the attribute inside a section. =cut sub readOnlySystemConfiguration(;$) { my %configuration = (); my $systemFile = getSystemConfigFilename(); readConfigFile("$systemFile",\%configuration,$_[0]); # Remove the main intput filename if (exists $configuration{'generation.main file'}) { delete $configuration{'generation.main file'}; } return %configuration; } =pod =item B Replies the current configuration. The configuration is extracted from the user configuration file ($HOME/.autolatex or $HOME/.autolatex/autolatex.conf) only. I a hashtable containing (attribute name, attribute value) pairs. The attribute name could be S to describe the attribute inside a section. =cut sub readOnlyUserConfiguration(;$) { my %configuration = (); my $userFile = getUserConfigFilename(); readConfigFile("$userFile",\%configuration,$_[0]); # Remove the main intput filename if (exists $configuration{'generation.main file'}) { delete $configuration{'generation.main file'}; } return %configuration; } =pod =item B Replies the current configuration. The configuration is extracted from the project configuration file ($PROJECT_PATH/.autolatex_project.cfg) only. I a hashtable containing (attribute name, attribute value) pairs. The attribute name could be S to describe the attribute inside a section. =cut sub readOnlyProjectConfiguration(@) { my %configuration = (); my $userFile = getProjectConfigFilename(@_); if (-r "$userFile") { readConfigFile("$userFile",\%configuration); $configuration{'__private__'}{'input.project directory'} = File::Spec->catfile(@_); return \%configuration; } return undef; } =pod =item B Fill the configuration data structure from the specified file information. The structure of the filled hashtable is a set of (attribute name, attribute value) pairs. The attribute name could be S to describe the attribute inside a section. I =over 8 =item * the name of the file to read =item * the data structure to fill =item * boolean value that indicates if a warning message should be ignored when an old fashion file was detected. =back I nothing =cut sub readConfigFile($\%;$) { my $filename = shift; die('second parameter of readConfigFile() is not a hash') unless(isHash($_[0])); printDbg(formatText(_T("Opening configuration file {}"),$filename)); if (-r "$filename") { my $cfgReader = new Config::Simple("$filename"); if ($cfgReader) { my %config = $cfgReader->vars(); my $warningDisplayed = $_[1]; while (my ($k,$v) = each (%config)) { $k = lc("$k"); if ($k !~ /^__private__/) { ($k,$v) = ensureAccendentCompatibility("$k",$v,"$filename",$warningDisplayed); $v = rebuiltConfigValue("$k",$v); if ($v) { $_[0]->{"$k"} = $v; } } } } printDbg(_T("Succeed on reading")); } else { printDbg(formatText(_T("Failed to read {}: {}"),$filename,$!)); } 1; } # Put formatted comments inside an array sub pushComment(\@$;$) { my $limit = $_[2] || 60; $limit = 1 unless ($limit>=1); my @lines = split(/\n/, $_[1]); foreach my $line (@lines) { my @words = split(/\s+/, $line); my $wline = ''; if ($line =~ /^(\s+)/) { $wline .= $1; } foreach my $w (@words) { if ((length($wline)+length($w)+1)>$limit) { push @{$_[0]}, "#$wline\n"; if (length($w)>$limit) { while (length($w)>$limit) { push @{$_[0]}, "# ".substr($w,0,$limit)."\n"; $w = substr($w,$limit); } } $wline = " $w"; } else { $wline .= " $w"; } } if ($wline) { push @{$_[0]}, "#$wline\n"; } } } # Reformat the value to be written inside a configuration file. # $_[0]: value name, # $_[1]: value to validated. sub serializeConfigValue($$) { my $v = $_[1]; if (isArray($v)) { $v = ''; foreach my $ev (@{$_[1]}) { if ($v) { $v .= ', '; } $v .= &serializeConfigValue($_[0], $ev); } } elsif (isHash($v)) { $v = ''; while (my ($key, $value) = each(%{$_[1]})) { if ($v) { $v .= ', '; } $v .= "$key:{"; $v .= &serializeConfigValue($_[0], $value); $v .= "}"; } } return $v; } =pod =item B Write the specified configuration into a file. I =over 8 =item * the name of the file to write =item * the configuration data structure to write =item * (optional) indicates if the comments must be written =back I nothing =cut sub writeConfigFile($\%;$) { my $filename = shift; die('second parameter of writeConfigFile() is not a hash') unless(isHash($_[0])); # Write the values printDbg(formatText(_T("Writing configuration file {}"),$filename)); printDbgIndent(); my $cfgWriter = new Config::Simple(syntax=>'ini'); my $has = 0; while (my ($attr,$value) = each (%{$_[0]})) { if ($value && $attr ne '__private__') { $value = serializeConfigValue($attr, $value); if ($value) { $cfgWriter->param("$attr",$value); $has = 1; } } } if ($has) { $cfgWriter->write("$filename") or printErr($cfgWriter->error()); if ($_[1]) { # Updating for comments printDbg(_T("Adding configuration comments")); local *CFGFILE; open (*CFGFILE, "< $filename") or printErr("$filename:","$!"); my @lines = (); my $lastsection = undef; while (my $l = ) { if ($l =~ /^\s*\[\s*(.+?)\s*\]\s*$/) { $lastsection = lc($1); if ($SECTION_COMMENTS{"$lastsection"}) { push @lines, "\n"; pushComment @lines, $SECTION_COMMENTS{"$lastsection"}; } else { push @lines, "\n"; pushComment @lines, _T("Configuration of the translator")." '$lastsection'"; } } elsif (($l =~ /^\s*(.*?)\s*=/)&&($lastsection)) { my $attr = lc($1); if ($CONFIGURATION_COMMENTS{"$lastsection.$attr"}) { push @lines, "\n"; pushComment @lines, $CONFIGURATION_COMMENTS{"$lastsection.$attr"}; } } push @lines, $l; } close(*CFGFILE); printDbg(_T("Saving configuration comments")); local *CFGFILE; open (*CFGFILE, "> $filename") or printErr("$filename:","$!"); print CFGFILE (@lines); close(*CFGFILE); } } else { # Create an empty file open (*CFGFILE, "> $filename") or printErr("$filename:","$!"); close(*CFGFILE); } printDbgUnindent(); 1; } =pod =item B Fix the specified configuration file. I =over 8 =item * the name of the file to fix =back I nothing =cut sub doConfigurationFileFixing($) { my $filename = shift; my %configuration = (); readConfigFile("$filename",%configuration,1); writeConfigFile("$filename",%configuration); 1; } # Try to detect an old fashioned configuration file # and fix the value sub ensureAccendentCompatibility($$$$) { my $k = $_[0]; my $v = $_[1]; my $changed = 0; $v = '' unless (defined($v)); if ($k eq 'generation.bibtex') { $k = 'generation.biblio'; $changed = 1; } if (!isArray($v)) { # Remove comments on the same line as values if ($v =~ /^\s*(.*?)\s*\#.*$/) { $v = "$1" ; $changed = 1; } if ($v eq '@detect@system') { $v = ['detect','system']; $changed = 1; } } if (($changed)&&(!$_[3])) { printWarn(formatText(_T("AutoLaTeX has detecting an old fashion syntax for the configuration file {}\nPlease regenerate this file with the command line option --fixconfig."), $_[2])); $_[3] = 1; } return ($k,$v); } # Reformat the value from a configuration file to apply several rules # which could not be directly applied by the configuration readed. # $_[0]: value name, # $_[1]: value to validated. sub rebuiltConfigValue($$) { my $v = $_[1]; if (($_[0])&&($v)) { my $v_str = trim($v); # If the string "empty value" is detected, delete the value if ($v_str eq EMPTY_INI_VALUE) { $v = ''; } # Split the include path of translators elsif ($_[0] eq 'generation.translator include path') { my $sep = getPathListSeparator(); if (isHash($v)) { while (my ($key,$val) = each(%{$v})) { my @tab = split(/\s*$sep\s*/,"$val"); if (@tab>1) { $v->{"$key"} = @tab; } else { $v->{"$key"} = pop @tab; } } } else { my @paths = (); if (isArray($v)) { foreach my $p (@{$v}) { push @paths, split(/\s*$sep\s*/,"$p"); } } else { push @paths, split(/\s*$sep\s*/,"$v"); } if (@paths>1) { $v = \@paths; } else { $v = pop @paths; } } } } return $v; } =pod =item B Set the translator inclusion flags obtained from the configurations. This function assumed that the translator list is an hashtable of (translator_name => { 'included' => { level => boolean } }) pairs. I =over 8 =item * the translator list. =item * the system configuration. =item * the user configuration. =item * the project configuration. =back I nothing =cut sub setInclusionFlags(\%\%;\%\%) { die('first parameter of setInclusionFlags() is not a hash') unless (isHash($_[0])); die('second parameter of setInclusionFlags() is not a hash') unless (isHash($_[1])); foreach my $trans (keys %{$_[0]}) { if (!$_[0]->{"$trans"}{'included'}) { $_[0]->{"$trans"}{'included'} = {}; } if ((exists $_[1]->{"$trans.include module"})&&(cfgIsBoolean($_[1]->{"$trans.include module"}))) { $_[0]->{"$trans"}{'included'}{'system'} = cfgBoolean($_[1]->{"$trans.include module"}); } else { # On system level, a module which was not specified as not includable must # be included even if it will cause conflicts $_[0]->{"$trans"}{'included'}{'system'} = undef; } if (($_[2])&& (exists $_[2]->{"$trans.include module"})&& (cfgIsBoolean($_[2]->{"$trans.include module"}))) { $_[0]->{"$trans"}{'included'}{'user'} = cfgBoolean($_[2]->{"$trans.include module"}); } else { $_[0]->{"$trans"}{'included'}{'user'} = undef; } if (($_[3])&& (exists $_[3]->{"$trans.include module"})&& (cfgIsBoolean($_[3]->{"$trans.include module"}))) { $_[0]->{"$trans"}{'included'}{'project'} = cfgBoolean($_[3]->{"$trans.include module"}); } else { $_[0]->{"$trans"}{'included'}{'project'} = undef; } } } =pod =item B Init the translator inclusion flags obtained from the configurations. This function assumed that the translator list is an hashtable of (translator_name => { 'included' => { level => undef } }) pairs. I =over 8 =item * the translator list. =item * the system configuration. =item * the user configuration. =item * the project configuration. =back I nothing =cut sub reinitInclusionFlags(\%\%;\%\%) { die('first parameter of setInclusionFlags() is not a hash') unless (isHash($_[0])); die('second parameter of setInclusionFlags() is not a hash') unless (isHash($_[1])); foreach my $trans (keys %{$_[0]}) { if (!$_[0]->{"$trans"}{'included'}) { $_[0]->{"$trans"}{'included'} = {}; } $_[0]->{"$trans"}{'included'}{'system'} = undef; if ($_[2]) { $_[0]->{"$trans"}{'included'}{'user'} = undef; } if ($_[3]) { $_[0]->{"$trans"}{'included'}{'project'} = undef; } } } 1; __END__ =back =head1 BUG REPORT AND FEEDBACK To report bugs, provide feedback, suggest new features, etc. (in prefered order): a) visit the developer site on GitHub , b) visit the AutoLaTeX main page , or c) send email to the main author at galland@arakhne.org. =head1 LICENSE S =head1 COPYRIGHT Sgalland@arakhne.orgE> =head1 SEE ALSO L