#! /usr/bin/perl # texi2any: Texinfo converter. # # Copyright 2010-2026 Free Software Foundation, Inc. # # 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 3 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. If not, see . # # Original author: Patrice Dumas # Parts (also from Patrice Dumas) come from texi2html.pl or texi2html.init. # # ALTIMP ../C/texi2any.c # The system of reference counting used by Perl to release memory # does not work if there are cycles. For the last input file, nothing # is done as the memory is released faster at exit. If there are # XS extensions in C, their memory is not cleanup either for the last # input file. # # If TEST is set, and for the input files other than the last one, # the cycles are removed (when resetting converters and destroying # document), and the XS extensions memory is cleaned, at least such # that there is no unreachable memory. For output units, in addition # to removing cycles, all references to output units and tree elements # are removed. # # If TEST is > 1, in addition # * the reference to elements are removed (with $remove_references set) # * checks on remaining refcounts are shown. # These two effects are triggered by TEST, but are checked at different # places such that the conditions can be changed independently. use 5.006; use strict; use warnings; # Through rules in Makefile.am, directory paths set through configure are # substituted directly in strings in the code, for example # $datadir = '/usr/share'; # We always use these strings as byte string, therefore we explicitly # set no utf8 to be sure that strings in code will never be considered as # character strings by Perl. no utf8; # check that autovivification do not happen incorrectly. #no autovivification qw(fetch delete exists store strict); # to decode command line arguments use Encode qw(decode encode find_encoding); # for file names portability use File::Spec; # to determine the path separator and null file use Config; # for dirname and fileparse use File::Basename; #use Cwd; use Getopt::Long qw(GetOptions); # for carp #use Carp; # for dclone use Storable; #use Data::Dumper; #use Devel::Cycle; #use Devel::Peek; eval { require Devel::FindRef; Devel::FindRef->import(); }; eval { require Devel::Refcount; Devel::Refcount->import(); }; Getopt::Long::Configure("gnu_getopt"); my ($real_command_name, $command_directory, $command_suffix); my $datadir; # This big BEGIN block deals with finding modules and # some dependencies that we ship # * in source or # * installed or # * installed relative to the script BEGIN { ($real_command_name, $command_directory, $command_suffix) = fileparse($0, '.pl'); my $updir = File::Spec->updir(); # These are substituted by the Makefile to create "texi2any". my $libdir = '/usr/lib64'; my $converter_libdir; if ('/usr/share' eq '@' .'datadir@' or defined($ENV{'TEXINFO_DEV_SOURCE'}) and $ENV{'TEXINFO_DEV_SOURCE'} ne '0') { # use installed path for datadir, even if uninstalled. datadir, # however, is only used for some directories. if ('/usr/share' eq '@' .'datadir@') { my $fallback_prefix = File::Spec->rootdir() . join('/', ('usr', 'local')); $datadir = "$fallback_prefix/share"; } else { $datadir = '/usr/share'; } # Use uninstalled modules # To find Texinfo::ModulePath if (defined($ENV{'t2a_builddir'})) { unshift @INC, join('/', ($ENV{'t2a_builddir'}, 'perl')); } else { unshift @INC, $command_directory; } eval { require Texinfo::ModulePath; }; if ($@ ne '') { if (-l $0) { my $followed = readlink($0); if ($followed) { ($real_command_name, $command_directory, $command_suffix) = fileparse($followed, '.pl'); unshift @INC, $command_directory; eval { require Texinfo::ModulePath; }; } } if ($@ ne '') { die "couldn't load Texinfo::ModulePath: $@\n"; } } Texinfo::ModulePath::init(undef, undef, undef, 'updirs' => 1); } else { # Look for modules in their installed locations. $datadir = '/usr/share'; my $converter = 'texi2any'; my $modules_dir = join('/', ($datadir, $converter)); # look for package data in the installed location. $converter_libdir = join('/', ($libdir, $converter)); # try to make package relocatable, will only work if # standard relative paths are used if (! -f join('/', ($modules_dir, 'Texinfo', 'Parser.pm')) and -f join('/', ($command_directory, $updir, 'share', $converter, 'Texinfo', 'Parser.pm'))) { $datadir = join('/', ($command_directory, $updir, 'share')); $modules_dir = join('/', ($datadir, $converter)); $converter_libdir = join('/', ($command_directory, $updir, 'lib', $converter)); } unshift @INC, $modules_dir; require Texinfo::ModulePath; Texinfo::ModulePath::init($modules_dir, $converter_libdir, $datadir, 'installed' => 1); } } # end BEGIN # This is not useful as this is already done in Texinfo::XSLoader based # on the enable_xs information from Texinfo::ModulePath. ## This allows disabling use of XS modules when Texinfo is built. #BEGIN { # my $enable_xs = 'no'; # if ($enable_xs eq 'no') { # package Texinfo::XSLoader; # our $disable_XS; # $disable_XS = 1; # } #} use Texinfo::XSLoader; use Locale::Messages; use Texinfo::Options; use Texinfo::Common; use Texinfo::Config; use Texinfo::Report; # determine the path separators my $path_separator = $Config{'path_sep'}; $path_separator = ':' if (!defined($path_separator)); my $quoted_path_separator = quotemeta($path_separator); # Paths and file names my $curdir = File::Spec->curdir(); my $updir = File::Spec->updir(); my $sysconfdir; my $converter; my $fallback_prefix = File::Spec->rootdir() . join('/', ('usr', 'local')); # The @ delimited strings are substituted by the Makefile to create "texi2any". if ('/etc' ne '@' . 'sysconfdir@') { $sysconfdir = '/etc'; } else { $sysconfdir = "$fallback_prefix/etc"; } if ('texi2any' ne '@' . 'CONVERTER@') { $converter = 'texi2any'; } else { $converter = 'texi2any'; } my $extensions_dir; if ($Texinfo::ModulePath::texinfo_uninstalled) { $extensions_dir = join('/', ($Texinfo::ModulePath::t2a_srcdir, 'perl', 'ext')); } else { $extensions_dir = join('/', ($Texinfo::ModulePath::converter_datadir, 'ext')); } my $internal_extension_dirs = [$extensions_dir]; # the encoding used to decode command line arguments, and also for # file names encoding, Perl is expecting sequences of bytes, not unicode # code points. my $locale_encoding; # the encoding used to encode messages. my $console_output_encoding; eval 'require I18N::Langinfo'; if (!$@) { my $langinfo_locale_encoding = I18N::Langinfo::langinfo(I18N::Langinfo::CODESET()); if (defined($langinfo_locale_encoding) and $langinfo_locale_encoding ne '') { $locale_encoding = $langinfo_locale_encoding; $console_output_encoding = $langinfo_locale_encoding; } } if (!defined($locale_encoding) and $^O eq 'MSWin32') { eval 'require Win32::API'; if (!$@) { my $win32_utf8_codepage = '65001'; Win32::API::More->Import("kernel32", "int GetACP()"); my $CP = GetACP(); if (defined($CP)) { if ($CP eq $win32_utf8_codepage) { $locale_encoding = 'UTF-8'; } else { $locale_encoding = 'cp'.$CP; } } Win32::API::More->Import("kernel32", "int GetConsoleOutputCP()"); my $CP_output = GetConsoleOutputCP(); if (defined($CP_output)) { if ($CP_output eq $win32_utf8_codepage) { $console_output_encoding = 'UTF-8'; } else { $console_output_encoding = 'cp'.$CP_output; } } } } # initial setup of messages internalisation framework # work-around in case libintl-perl do not do it itself # see http://www.gnu.org/software/gettext/manual/html_node/The-LANGUAGE-variable.html#The-LANGUAGE-variable if ((defined($ENV{"LC_ALL"}) and $ENV{"LC_ALL"} =~ /^(C|POSIX)$/) or (defined($ENV{"LANG"}) and $ENV{"LANG"} =~ /^(C|POSIX)$/)) { delete $ENV{"LANGUAGE"} if defined($ENV{"LANGUAGE"}); } #my $messages_textdomain = 'texinfo'; my $messages_textdomain = 'texinfo'; $messages_textdomain = 'texinfo' if ($messages_textdomain eq '@'.'PACKAGE@'); my $strings_textdomain = 'texinfo' . '_document'; $strings_textdomain = 'texinfo_document' if ($strings_textdomain eq '@'.'PACKAGE@' . '_document'); # we want a reliable way to switch locale, so we don't use the system # gettext. Locale::Messages->select_package('gettext_pp'); # Note: this uses installed or fallback directory messages when # the program is uninstalled Locale::Messages::bindtextdomain($messages_textdomain, join('/', ($datadir, 'locale'))); # Set initial configuration # We use the configured version for version. If not set we search in # configure.ac. # We do not fallback on a Texinfo module version to be able to # verify that there is no mismatch. # Version set in configure.ac and substituted in Makefile my $configured_version = '7.3'; if ($configured_version eq '@' . 'PACKAGE_VERSION@') { # if not configured/substituted, search for the version in configure.ac if (open(CONFIGURE, "< " . join('/', ($Texinfo::ModulePath::t2a_srcdir, 'configure.ac')))) { while () { if (/^AC_INIT\(\[[^\]]+\]\s*,\s*\[([^\]]+)\]\s*[,\)]/) { # add +nc to distinguish from configured and, in general, installed. # If called from build directory with TEXINFO_DEV_SOURCE=1, however # there will not be +nc as the $configured_version is set. $configured_version = "$1+nc"; last; } } close(CONFIGURE); } } if (!defined($configured_version)) { die "Cannot determine the texi2any version; aborting.\n"; } # Compare the version of this file with the version of the modules # it is using. If they are different, don't go any further. This # can happen if multiple versions are installed under a # different names, e.g. with the --program-suffix option to 'configure'. # The version in Common.pm is checked because that file has been present # since Texinfo 5.0 (the first release with texi2any in Perl). if ($configured_version ne $Texinfo::Common::VERSION and $configured_version ne $Texinfo::Common::VERSION."+nc") { warn "This is texi2any $configured_version but modules ". "for texi2any $Texinfo::Common::VERSION found!\n"; die "Your installation of Texinfo is broken; aborting.\n"; } my $configured_package = 'texinfo'; $configured_package = 'texinfo' if ($configured_package eq '@' . 'PACKAGE@'); my $configured_name = 'GNU Texinfo'; $configured_name = 'GNU Texinfo' if ($configured_name eq '@' .'PACKAGE_NAME@'); my $configured_name_version = "$configured_name $configured_version"; my $configured_url = 'https://www.gnu.org/software/texinfo/'; $configured_url = 'https://www.gnu.org/software/texinfo/' if ($configured_url eq '@' .'PACKAGE_URL@'); my $texinfo_dtd_version = '7.3'; if ($texinfo_dtd_version eq '@' . 'TEXINFO_DTD_VERSION@') { $texinfo_dtd_version = undef; if (open(CONFIGURE, "< ".join('/', ($Texinfo::ModulePath::t2a_srcdir, 'configure.ac')))) { while () { if (/^TEXINFO_DTD_VERSION=([0-9]\S*)/) { $texinfo_dtd_version = "$1"; last; } } close(CONFIGURE); } } # if not configured nor found in configure.ac $texinfo_dtd_version = $configured_version if (!defined($texinfo_dtd_version)); my $configured_information = { 'PACKAGE_VERSION' => $configured_version, 'PACKAGE' => $configured_package, 'PACKAGE_NAME' => $configured_name, 'PACKAGE_AND_VERSION' => $configured_name_version, 'PACKAGE_URL' => $configured_url, }; # options set in the main program. my $main_program_set_options = { 'PROGRAM' => $real_command_name, 'TEXINFO_DTD_VERSION' => $texinfo_dtd_version, # Used for : # * decoding command-line, including file names # * encoding command-line before executing a command # * error and warning messages translations encoding 'COMMAND_LINE_ENCODING' => $locale_encoding, # Used for error and warning messages output encoding 'MESSAGE_ENCODING' => $console_output_encoding, # Used to encode input file names and output file names, if # DOC_ENCODING_FOR_INPUT_FILE_NAME and DOC_ENCODING_FOR_OUTPUT_FILE_NAME # respectively are not set. 'LOCALE_ENCODING' => $locale_encoding, # better than making it the default value independently of the implementation 'TEXINFO_OUTPUT_FORMAT' => 'info', }; # set configure information as constants foreach my $configured_variable (keys(%$configured_information)) { Texinfo::Common::set_build_constant($configured_variable, $configured_information->{$configured_variable}); # set also with _CONFIG prepended, as in C code. Texinfo::Common::set_build_constant($configured_variable.'_CONFIG', $configured_information->{$configured_variable}); } foreach my $configured_variable (keys(%$configured_information)) { $main_program_set_options->{$configured_variable} = $configured_information->{$configured_variable}; } # defaults for options relevant in the main program. Also used as # defaults for all the converters. my $main_program_default_options = { %$main_program_set_options, %Texinfo::Common::default_main_program_customization_options, }; # In Windows, a character in file name is encoded according to the current # codepage, and converted to/from UTF-16 in the filesystem. If a file name is # not encoded in the current codepage, the file name will appear with erroneous # characters when listing file names. Also the encoding and decoding to # UTF-16 may fail, especially when the codepage is 8bit while the file name # is encoded in a multibyte encoding. # We assume that in Windows the file names are reencoded in the current # codepage encoding to avoid those issues. if ($^O eq 'MSWin32') { $main_program_set_options->{'DOC_ENCODING_FOR_INPUT_FILE_NAME'} = 0; } # determine configuration directories. # used as part of binary strings my $conf_file_name = 'texi2any-config.pm'; # When we replace a directory in a search path, we issue a warning for a while, # using %deprecated_directories to match the directory that # should be used. # In 2024 we switched to using the XDG Base Directory Specification, # https://specifications.freedesktop.org/basedir-spec/latest/index.html # $HOME/.texinfo should be $XDG_CONFIG_HOME default: $HOME/.config/texinfo # # We do not follow strictly the XDG Base Directory Specification, we use and # prefer installation directories, and we do not use the default absolute # directories. my %deprecated_directories; # Return the search path for a subdirectory in system directories. # The order is # installation directory # XDG environment variable directories # Input: # - $ENV_STRING the XDG Base Directory Specification env variable name # - $SUBDIR: the subdirectory setup in configuration path search # - $DEFAULT_BASE_DIRS: ignored parameter. # Default XDG Base Directory Specification absolute # directories; supposed to be last in search paths. # - $INSTALLATION_DIRS: directories specified as installation directory # (using configured directory names) for this $SUBDIR # - $OVERRIDING_DIRS: associate new base directories to deprecated base # directory locations # # Output: # - $DEPRECATED_DIRS: associate deprecated SUBDIR location to expected # location sub add_config_paths($$$$;$$) { my ($env_string, $subdir, $default_base_dirs, $installation_dir, $overriding_dirs, $deprecated_dirs) = @_; # read the env directories first to avoid setting the overriding_dirs # as deprecated if they are explicitely specified in the environnement # variable. my @xdg_result_dirs; my %used_xdg_base_dirs; if (defined($ENV{$env_string}) and $ENV{$env_string} ne '') { foreach my $dir (split(/$quoted_path_separator/, $ENV{$env_string})) { if ($dir ne '') { push @xdg_result_dirs, $dir; $used_xdg_base_dirs{$dir} = 1; } } } my @result_dirs; my %used_base_dirs; if (defined($installation_dir)) { my $install_result_dir = "$installation_dir/$subdir"; push @result_dirs, $install_result_dir; $used_base_dirs{$installation_dir} = 1; if ($overriding_dirs and $overriding_dirs->{$installation_dir}) { my $deprecated_dir = $overriding_dirs->{$installation_dir}; my $deprecated_result_dir = "$deprecated_dir/$subdir"; if (not exists($used_xdg_base_dirs{$deprecated_dir})) { my $deprecated_result_dir = "$deprecated_dir/$subdir"; $deprecated_dirs->{$deprecated_result_dir} = $install_result_dir; push @result_dirs, $deprecated_result_dir; $used_base_dirs{$deprecated_dir} = 1; } } } foreach my $dir (@xdg_result_dirs) { if (!exists($used_base_dirs{$dir})) { push @result_dirs, "$dir/$subdir"; $used_base_dirs{$dir} = 1; } } # to also use XDG Base Directory Specification defaults #foreach my $dir (@$default_base_dirs) { # if (!$used_base_dirs{$dir}) { # push @result_dirs, "$dir/$subdir"; # } #} return \@result_dirs; } # the search order is: # .$SUBDIR in current directory # $SUBDIR below XDG_CONFIG_HOME or HOME # $SUBDIR in system directories for configuration # installation directory # XDG environment variable directories # $DATADIR/$SUBDIR sub set_subdir_directories($$$) { my ($datadir, $subdir, $deprecated_dirs) = @_; my @result = (".$subdir"); my $config_home; my $deprecated_config_home; if (defined($ENV{'XDG_CONFIG_HOME'}) and $ENV{'XDG_CONFIG_HOME'} ne '') { $config_home = $ENV{'XDG_CONFIG_HOME'}."/$subdir"; } else { if (defined($ENV{'HOME'})) { $config_home = join('/', ($ENV{'HOME'}, '.config', $subdir)); $deprecated_config_home = $ENV{'HOME'}.'/.'.$subdir; $deprecated_dirs->{$deprecated_config_home} = $config_home; } } push @result, $config_home if (defined($config_home)); push @result, $deprecated_config_home if (defined($deprecated_config_home)); my $sysconf_install_dir = "$sysconfdir/xdg"; # associate new location to deprecated location my $overriding_dirs = {$sysconf_install_dir => $sysconfdir}; # in 2024, mark $sysconfdir overriden by $sysconfdir/xdg. my $config_dirs = add_config_paths('XDG_CONFIG_DIRS', $subdir, ['/etc/xdg'], "$sysconfdir/xdg", $overriding_dirs, $deprecated_dirs); push @result, @$config_dirs; # Do not use XDG base specification for directories and files in # datadir, there is no need for customization of those directories # since the sysconfdir directories are already customized, just use # the installation directory. # Note: this uses installed or fallback directory when # the program is uninstalled push @result, "$datadir/$subdir"; # the following code could have been used to use XDG_DATA_DIRS for # datadir directories and files too #my $data_dirs = add_config_paths('XDG_DATA_DIRS', $subdir, # ['/usr/local/share/', '/usr/share/'], $datadir); #push @result, @$data_dirs; return \@result; } # directories for Texinfo configuration files, as far as possible # implementation independent. Used as part of binary strings. # curdir and the input file path directory are prepended later on. my $language_config_dirs = set_subdir_directories($datadir, 'texinfo', \%deprecated_directories); my @texinfo_language_config_dirs = @$language_config_dirs; # these variables are used as part of binary strings. my @converter_config_dirs; my @converter_init_dirs; # implementation (texi2any) specific directories my $converter_config_dirs_array_ref = set_subdir_directories($datadir, $converter, \%deprecated_directories); @converter_config_dirs = ($curdir, @$converter_config_dirs_array_ref); # Directories searched for customization code files, mainly for HTML. @converter_init_dirs = @converter_config_dirs; foreach my $texinfo_config_dir (@texinfo_language_config_dirs) { my $init_dir = "$texinfo_config_dir/init"; push @converter_init_dirs, $init_dir; if (exists($deprecated_directories{$texinfo_config_dir})) { $deprecated_directories{$init_dir} = "$deprecated_directories{$texinfo_config_dir}/init"; } } # add texi2any extensions dir too, such as the init files there # can also be loaded as regular init files. push @converter_init_dirs, $extensions_dir; #print STDERR join("\n", @converter_init_dirs)."\n\n"; #print STDERR join("\n", sort(keys(%deprecated_directories)))."\n"; sub _decode_i18n_string($$) { my ($string, $encoding) = @_; return decode($encoding, $string); } sub _encode_message($) { my $text = shift; my $encoding = get_conf('MESSAGE_ENCODING'); if (defined($encoding)) { return encode($encoding, $text); } else { return $text; } } sub document_warn($) { return if (get_conf('NO_WARN')); my $text = shift; warn(_encode_message( sprintf(__p("program name: warning: warning_message", "%s: warning: %s")."\n", $real_command_name, $text))); } sub _decode_input($) { my $text = shift; my $encoding = get_conf('COMMAND_LINE_ENCODING'); if (defined($encoding)) { return decode($encoding, $text); } else { return $text; } } sub _warn_deprecated_dirs($$) { my ($deprecated_dirs, $deprecated_dirs_used) = @_; foreach my $dir (@$deprecated_dirs_used) { my $dir_name = _decode_input($dir); my $replacement_dir = _decode_input($deprecated_dirs->{$dir}); document_warn(sprintf(__( "%s directory is deprecated. Use %s instead"), $dir_name, $replacement_dir)); } } # arguments are binary strings. sub locate_and_load_init_file($$;$) { my ($filename, $directories, $deprecated_dirs) = @_; my ($files, $deprecated_dirs_used) = Texinfo::Common::locate_file_in_dirs($filename, $directories, 0, $deprecated_dirs); if (defined($files)) { my $file = $files->[0]; # evaluate the code in the Texinfo::Config namespace Texinfo::Config::GNUT_load_init_file($file); } else { document_warn(sprintf(__("could not read init file %s"), _decode_input($filename))); } if (defined($deprecated_dirs) and defined($deprecated_dirs_used)) { _warn_deprecated_dirs($deprecated_dirs, $deprecated_dirs_used); } } # arguments are binary strings. # Init files that are used in texi2any, considered # as internal extensions code. sub locate_and_load_extension_file($$) { my ($filename, $directories) = @_; # no possible deprecated dirs with the path passed to this sub my ($files, $deprecated_dirs_used) = Texinfo::Common::locate_file_in_dirs($filename, $directories, 0); if (defined($files)) { # evaluate the code in the Texinfo::Config namespace my $file = $files->[0]; Texinfo::Config::GNUT_load_init_file($file); } else { die _encode_message(sprintf(__("could not read extension file %s"), _decode_input($filename))); } } sub set_from_cmdline($$) { return &Texinfo::Config::GNUT_set_from_cmdline(@_); } sub set_main_program_default($$) { return &Texinfo::Config::GNUT_set_customization_default(@_); } sub get_conf($) { return &Texinfo::Config::texinfo_get_conf(@_); } sub add_to_option_list($$) { return &Texinfo::Config::texinfo_add_to_option_list(@_); } sub remove_from_option_list($$) { return &Texinfo::Config::texinfo_remove_from_option_list(@_); } sub set_translations_encoding($) { my $translations_encoding = shift; if (defined($translations_encoding) and $translations_encoding ne 'us-ascii') { my $Encode_encoding_object = find_encoding($translations_encoding); my $perl_translations_encoding = $Encode_encoding_object->name(); Locale::Messages::bind_textdomain_codeset($messages_textdomain, $translations_encoding); if (defined($perl_translations_encoding)) { Locale::Messages::bind_textdomain_filter($messages_textdomain, \&_decode_i18n_string, $perl_translations_encoding); } } } # Setup customization and read customization files processed each time # the program is run # this associates the command line options to the arrays set during # command line parsing. my @css_files = (); my @css_refs = (); my @include_dirs = (); my @expanded_formats = (); # note that CSS_FILES and INCLUDE_DIRECTORIES are not decoded when # read from the command line and should be binary strings. # TEXINFO_LANGUAGE_DIRECTORIES is not actually read from the command # line, but it is still best to have it here, and it should also # contain binary strings. my $cmdline_options = { 'CSS_FILES' => \@css_files, 'CSS_REFS' => \@css_refs, 'INCLUDE_DIRECTORIES' => \@include_dirs, 'TEXINFO_LANGUAGE_DIRECTORIES' => \@texinfo_language_config_dirs, 'EXPANDED_FORMATS' => \@expanded_formats }; my @conf_dirs = (); my @prepend_dirs = (); # The $cmdline_options passed to Texinfo::Config::GNUT_initialize_customization # are considered to be arrays in which items can be added or deleted both # from the command line and from init files. $cmdline_options text values # are set by GNUT_set_from_cmdline (aliased as set_from_cmdline) from the # main program. $cmdline_options are also accessed in main program. # $init_files_options are managed by Texinfo::Config, set by # texinfo_set_from_init_file in init files. # # There is in addition $parser_options for parser related information # that is not gathered otherwise. # The configuration values are later on copied over to the parser if # they are parser options. my $parser_options = {'values' => {'txicommandconditionals' => 1}}; my $init_files_options = Texinfo::Config::GNUT_initialize_customization( $real_command_name, $main_program_default_options, $cmdline_options); # Need to do that early for early messages my $translations_encoding = get_conf('COMMAND_LINE_ENCODING'); set_translations_encoding($translations_encoding); # read initialization files. Better to do that after # Texinfo::Config::GNUT_initialize_customization() in case loaded # files replace default options. my ($config_init_files, $deprecated_dirs_for_config_init) = Texinfo::Common::locate_file_in_dirs($conf_file_name, [ reverse(@converter_config_dirs) ], 1, \%deprecated_directories); if (defined($config_init_files)) { foreach my $file (@$config_init_files) { Texinfo::Config::GNUT_load_init_file($file); } } if (defined($deprecated_dirs_for_config_init)) { _warn_deprecated_dirs(\%deprecated_directories, $deprecated_dirs_for_config_init); } # reset translations encodings if COMMAND_LINE_ENCODING was reset my $set_translations_encoding = get_conf('COMMAND_LINE_ENCODING'); if (defined($set_translations_encoding) and (not defined($translations_encoding) or $set_translations_encoding ne $translations_encoding)) { $translations_encoding = $set_translations_encoding; set_translations_encoding($translations_encoding); } # Parse command line my %ignored_formats; sub set_expansion($$) { my ($region, $set) = @_; $set = 1 if (!defined($set)); if ($set) { add_to_option_list('EXPANDED_FORMATS', [$region]); delete $ignored_formats{$region}; } else { remove_from_option_list('EXPANDED_FORMATS', [$region]); $ignored_formats{$region} = 1; } } my %possible_split = ( 'chapter' => 1, 'section' => 1, 'node' => 1, ); my $format_from_command_line = 0; my %converter_format_expanded_region_name = ( 'texinfoxml' => 'xml', 'docbookreader' => 'docbook', 'docbooknoreader' => 'docbook', 'docbooktreeelementreader' => 'docbook', ); my %format_command_line_names = ( 'xml' => 'texinfoxml', ); my %formats_table = ( 'info' => { 'nodes_tree' => 1, 'floats' => 1, 'setup_index_entries_sort_strings' => 1, 'module' => 'Texinfo::Convert::Info' }, 'plaintext' => { 'nodes_tree' => 1, 'floats' => 1, 'split' => 1, 'setup_index_entries_sort_strings' => 1, 'module' => 'Texinfo::Convert::Plaintext' }, 'html' => { 'nodes_tree' => 1, 'floats' => 1, 'split' => 1, 'internal_links' => 1, 'move_index_entries_after_items' => 1, 'relate_index_entries_to_table_items' => 1, 'no_warn_non_empty_parts' => 1, 'setup_index_entries_sort_strings' => 1, 'module' => 'Texinfo::Convert::HTML' }, 'latex' => { 'floats' => 1, 'move_index_entries_after_items' => 1, 'no_warn_non_empty_parts' => 1, 'module' => 'Texinfo::Convert::LaTeX' }, 'texinfoxml' => { 'nodes_tree' => 1, 'module' => 'Texinfo::Convert::TexinfoXML', 'floats' => 1, }, 'texinfosxml' => { 'nodes_tree' => 1, 'module' => 'Texinfo::Convert::TexinfoSXML', 'floats' => 1, }, 'ixinsxml' => { # note that the Texinfo tree is converted to # 'texinfosxml', but the conversion as a whole # is 'ixinsxml', as Texinfo tree conversion is done # from within Texinfo::Example::IXINSXML 'nodes_tree' => 1, 'setup_index_entries_sort_strings' => 1, 'module' => 'Texinfo::Example::IXINSXML', 'floats' => 1, }, 'docbook' => { 'move_index_entries_after_items' => 1, 'no_warn_non_empty_parts' => 1, 'module' => 'Texinfo::Convert::DocBook' #'module' => 'Texinfo::Example::TreeElementReadDocBook' #'module' => 'Texinfo::Example::ReadDocBook' }, # next 3 formats are not documented since they should not be used except # for development/timing 'docbooknoreader' => { 'move_index_entries_after_items' => 1, 'no_warn_non_empty_parts' => 1, 'module' => 'Texinfo::Convert::DocBook' }, 'docbookreader' => { 'move_index_entries_after_items' => 1, 'no_warn_non_empty_parts' => 1, 'module' => 'Texinfo::Example::ReadDocBook' }, 'docbooktreeelementreader' => { 'move_index_entries_after_items' => 1, 'no_warn_non_empty_parts' => 1, 'module' => 'Texinfo::Example::TreeElementReadDocBook' }, 'epub3' => { 'converted_format' => 'html', 'init_file' => 'epub3.pm', }, 'pdf' => { 'texi2dvi_format' => 1, }, 'ps' => { 'texi2dvi_format' => 1, }, 'dvi' => { 'texi2dvi_format' => 1, }, 'dvipdf' => { 'texi2dvi_format' => 1, }, 'debugtree' => { 'split' => 1, 'module' => 'Texinfo::DebugTree' }, 'textcontent' => { 'module' => 'Texinfo::Convert::TextContent' }, 'rawtext' => { 'module' => 'Texinfo::Convert::Text' }, 'plaintexinfo' => { 'module' => 'Texinfo::Convert::PlainTexinfo' }, # not documented, only used for testing/development 'testreader' => { 'module' => 'Texinfo::Example::TestReader' }, 'parse' => { }, 'structure' => { 'nodes_tree' => 1, 'floats' => 1, 'split' => 1, }, ); my $call_texi2dvi = 0; my @texi2dvi_args = (); sub set_cmdline_format($) { my $set_format = shift; set_from_cmdline('TEXINFO_OUTPUT_FORMAT', $set_format); } sub set_format($) { my $set_format = shift; my $new_output_format; if (exists($format_command_line_names{$set_format})) { $new_output_format = $format_command_line_names{$set_format}; } else { $new_output_format = $set_format; } if (!exists($formats_table{$new_output_format})) { document_warn(sprintf(__( "ignoring unrecognized TEXINFO_OUTPUT_FORMAT value `%s'"), $set_format)); } else { Texinfo::Config::texinfo_set_from_init_file('TEXINFO_OUTPUT_FORMAT', $new_output_format); } } sub _format_expanded_formats($) { my $new_output_format = shift; my $default_expanded_formats = {}; my $converter_format; my $expanded_region; if (exists($formats_table{$new_output_format}->{'texi2dvi_format'})) { $call_texi2dvi = 1; push @texi2dvi_args, '--'.$new_output_format; $converter_format = 'tex'; } elsif (exists($formats_table{$new_output_format}->{'converted_format'})) { $converter_format = $formats_table{$new_output_format}->{'converted_format'}; } else { $converter_format = $new_output_format; } if (exists($converter_format_expanded_region_name{$converter_format})) { $expanded_region = $converter_format_expanded_region_name{$converter_format}; } else { $expanded_region = $converter_format; } if (exists($Texinfo::Common::texinfo_output_formats{$expanded_region})) { if ($expanded_region eq 'plaintext') { $default_expanded_formats = {$expanded_region => 1, 'info' => 1}; } else { $default_expanded_formats = {$expanded_region => 1}; } } return $default_expanded_formats; } sub _get_converter_default($) { my $option = shift; if (defined($Texinfo::Options::converter_cmdline_options{$option})) { return $Texinfo::Options::converter_cmdline_options{$option}; } elsif (defined($Texinfo::Options::multiple_at_command_options{$option})) { return $Texinfo::Options::multiple_at_command_options{$option}; } return undef; } sub makeinfo_help() { my $makeinfo_help = sprintf(__("Usage: %s [OPTION]... TEXINFO-FILE..."), $real_command_name . $command_suffix) ."\n\n". __("Translate Texinfo source documentation to various other formats, by default Info files suitable for reading online with Emacs or standalone GNU Info. This program is commonly installed as both `makeinfo' and `texi2any'; the behavior is identical, and does not depend on the installed name.")."\n" ."\n"; $makeinfo_help .= __("General options:")."\n" .__(" --document-language=STR locale to use in translating Texinfo keywords for the output document (default C)")."\n" .sprintf(__(" --error-limit=NUM quit after NUM errors (default %d)"), get_conf('ERROR_LIMIT'))."\n" .__(" --force preserve output even if errors")."\n" .__(" --help display this help and exit")."\n" .__(" --no-validate suppress node cross-reference validation")."\n" .__(" --no-warn suppress warnings (but not errors)")."\n" .__(" --conf-dir=DIR search also for initialization files in DIR")."\n" .__(" --init-file=FILE load FILE to modify the default behavior")."\n" .__(" -c, --set-customization-variable VAR=VAL set customization variable VAR to value VAL")."\n" .__(" --trace-includes print names of included files")."\n" .__(" -v, --verbose explain what is being done")."\n" .__(" --version display version information and exit")."\n" ."\n"; $makeinfo_help .= __("Output format selection (default is to produce Info):")."\n" .__(" --docbook output Docbook XML")."\n" .__(" --html output HTML")."\n" .__(" --epub3 output EPUB 3")."\n" .__(" --latex output LaTeX")."\n" .__(" --plaintext output plain text rather than Info")."\n" .__(" --xml output Texinfo XML")."\n" .__(" --dvi, --dvipdf, --ps, --pdf call texi2dvi to generate given output, after checking validity of TEXINFO-FILE")."\n" ."\n"; $makeinfo_help .= __("General output options:")."\n" .__( " -E, --macro-expand=FILE output macro-expanded source to FILE, ignoring any \@setfilename")."\n" .__( " --no-headers suppress node separators, Node: lines, and menus from Info output (thus producing plain text) or from HTML (thus producing shorter output). Also, if producing Info, write to standard output by default.")."\n" .__( " --no-split suppress any splitting of the output; generate only one output file")."\n" .__( " --[no-]number-sections output chapter and sectioning numbers; default is on")."\n" .__( " --[no-]number-footnotes number footnotes sequentially; default is on")."\n" .__( " -o, --output=DEST output to DEST. With split output, create DEST as a directory and put the output files there. With non-split output, if DEST is already a directory or ends with a /, put the output file there. Otherwise, DEST names the output file.")."\n" .__( " --disable-encoding do not output accented and special characters in Info and plain text output based on document encoding")."\n" .__( " --enable-encoding override --disable-encoding (default)")."\n" ."\n"; $makeinfo_help .= sprintf(__("Options for Info and plain text:")."\n" .__( " --fill-column=NUM break Info lines at NUM columns (default %d)")."\n" .__( " --footnote-style=STYLE output footnotes in Info according to STYLE: `separate' to put them in their own node; `end' to put them at the end of the node, in which they are defined (this is the default)")."\n" .__( " --paragraph-indent=VAL indent Info paragraphs by VAL spaces (default %d). If VAL is `none', do not indent; if VAL is `asis', preserve existing indentation.")."\n" .__( " --split-size=NUM split Info files at size NUM (default %d)")."\n" ."\n", _get_converter_default('FILLCOLUMN'), _get_converter_default('paragraphindent'), _get_converter_default('SPLIT_SIZE')); $makeinfo_help .= __("Options for HTML:")."\n" .__(" --css-include=FILE include FILE in HTML