blob: 448fe651678f170cd74692d1aa5f602199be6021 [file]
#! /usr/bin/env 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 <https://www.gnu.org/licenses/>.
#
# Original author: Patrice Dumas <pertusus@free.fr>
# 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 = '@datadir@';
# 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 = '@libdir@';
my $converter_libdir;
if ('@datadir@' 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 ('@datadir@' eq '@' .'datadir@') {
my $fallback_prefix
= File::Spec->rootdir() . join('/', ('usr', 'local'));
$datadir = "$fallback_prefix/share";
} else {
$datadir = '@datadir@';
}
# 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 = '@datadir@';
my $converter = '@CONVERTER@';
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 = '@enable_xs@';
# 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 ('@sysconfdir@' ne '@' . 'sysconfdir@') {
$sysconfdir = '@sysconfdir@';
} else {
$sysconfdir = "$fallback_prefix/etc";
}
if ('@CONVERTER@' ne '@' . 'CONVERTER@') {
$converter = '@CONVERTER@';
} 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 = '@PACKAGE@';
$messages_textdomain = 'texinfo' if ($messages_textdomain eq '@'.'PACKAGE@');
my $strings_textdomain = '@PACKAGE@' . '_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 = '@PACKAGE_VERSION@';
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 (<CONFIGURE>) {
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 = '@PACKAGE@';
$configured_package = 'texinfo' if ($configured_package eq '@' . 'PACKAGE@');
my $configured_name = '@PACKAGE_NAME@';
$configured_name = 'GNU Texinfo'
if ($configured_name eq '@' .'PACKAGE_NAME@');
my $configured_name_version = "$configured_name $configured_version";
my $configured_url = '@PACKAGE_URL@';
$configured_url = 'https://www.gnu.org/software/texinfo/'
if ($configured_url eq '@' .'PACKAGE_URL@');
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,
# 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 explicitly 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 = (
'epub3' => 'epub',
'texinfoxml' => 'xml',
'docbookreader' => 'docbook',
'docbooknoreader' => 'docbook',
);
# nothing currently, previously mapped xml to texinfoxml
my %format_command_line_names = (
);
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::Example::TexinfoXML',
'floats' => 1,
},
'texinfosxml' => {
'nodes_tree' => 1,
'module' => 'Texinfo::Example::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::ReadDocBook'
},
# next 2 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'
},
'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'})) {
my $new_format_expanded_region;
$converter_format
= $formats_table{$new_output_format}->{'converted_format'};
# The converter format conditional is added below, here add output
# format specific conditional expansion if it exists.
# For epub3, for example, there is an epub specific conditional, in
# addition to html conditional setup because epub converter format
# is html
if (exists($converter_format_expanded_region_name{$new_output_format})) {
$new_format_expanded_region
= $converter_format_expanded_region_name{$new_output_format};
} else {
$new_format_expanded_region = $new_output_format;
}
if (exists($Texinfo::Common::texinfo_output_formats{
$new_format_expanded_region})) {
$default_expanded_formats->{$new_format_expanded_region} = 1;
}
} 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->{'info'} = 1;
}
$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"
.__(" --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 <style> output;
read stdin if FILE is -")."\n"
.__(" --css-ref=URL generate CSS reference to URL")."\n"
.__(" --internal-links=FILE produce list of internal links in FILE")."\n"
.__(" --split=SPLIT split at SPLIT, where SPLIT may be `chapter',
`section' or `node'")."\n"
.__(" --transliterate-file-names use file names in ASCII transliteration")."\n"
.__(" --node-files produce redirection files for nodes and
anchors; default is set only if split")
."\n\n";
$makeinfo_help .= __("Options for DVI/PS/PDF:")."\n"
.__(" --Xopt=OPT pass OPT to texi2dvi; can be repeated")."\n"
."\n";
$makeinfo_help .= __("Input file options:")."\n"
.__(" -D VAR define the variable VAR, as with \@set")."\n"
.__(" -D 'VAR VAL' define VAR to VAL (one shell argument)")."\n"
.__(" -I DIR append DIR to the \@include search path")."\n"
.__(" -P DIR prepend DIR to the \@include search path")."\n"
.__(" -U VAR undefine the variable VAR, as with \@clear")."\n"
."\n"
.__("Conditional processing in input:")."\n"
.__(" --ifdocbook process \@ifdocbook and \@docbook")."\n"
.__(" --ifepub process \@ifepub")."\n"
.__(" --ifhtml process \@ifhtml and \@html")."\n"
.__(" --ifinfo process \@ifinfo")."\n"
.__(" --iflatex process \@iflatex and \@latex")."\n"
.__(" --ifplaintext process \@ifplaintext")."\n"
.__(" --iftex process \@iftex and \@tex")."\n"
."\n"
.__(" By default, an --ifFORMAT setting is on only when the output format
is FORMAT; for example, --ifhtml is on when outputting HTML.
Also, when --ifFORMAT is on, \@ifnotFORMAT text is not processed.")."\n"
."\n"
.__(" Each --ifFORMAT option may be prefixed with \`--no-' to negate it;
for example, --no-ifhtml means not to process \@ifhtml or \@html text,
and to process \@ifnothtml text.")."\n"
."\n";
$makeinfo_help .= __("Examples:")."\n"
.sprintf(__(" %s foo.texi write Info"), $real_command_name)."\n"
.sprintf(__(" %s --html foo.texi write HTML"), $real_command_name)."\n"
.sprintf(__(" %s --docbook foo.texi write Docbook XML"), $real_command_name)."\n"
.sprintf(__(" %s --plaintext foo.texi write plain text to standard output"), $real_command_name)."\n"
.sprintf(__(" %s --pdf foo.texi write PDF using texi2dvi"), $real_command_name)."\n"
."\n"
.sprintf(__(" %s --html --no-headers foo.texi write html without node lines, menus"), $real_command_name)."\n"
.sprintf(__(" %s --number-sections foo.texi write Info with numbered sections"), $real_command_name)."\n"
.sprintf(__(" %s --no-split foo.texi write one Info file however big"), $real_command_name)."\n"
."\n";
$makeinfo_help .= __("Email bug reports to bug-texinfo\@gnu.org,
general questions and discussion to help-texinfo\@gnu.org.
Texinfo home page: https://www.gnu.org/software/texinfo/") ."\n";
return $makeinfo_help;
}
my $Xopt_arg_nr = 0;
my $result_options = Getopt::Long::GetOptions (
'help|h' => sub { print _encode_message(makeinfo_help()); exit 0; },
'version|V' => sub {
print _encode_message(
"$converter (GNU texinfo) $configured_version\n\n");
print _encode_message(sprintf __(
"Copyright (C) %s Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."), "2026")."\n";
exit 0;},
'macro-expand|E=s' => sub { set_from_cmdline('MACRO_EXPAND', $_[1]); },
'ifhtml!' => sub { set_expansion('html', $_[1]); },
'ifinfo!' => sub { set_expansion('info', $_[1]); },
'ifdocbook!' => sub { set_expansion('docbook', $_[1]); },
'iflatex!' => sub { set_expansion('latex', $_[1]); },
'iftex!' => sub { set_expansion('tex', $_[1]); },
'ifplaintext!' => sub { set_expansion('plaintext', $_[1]); },
'ifepub!' => sub { set_expansion('epub', $_[1]); },
'I=s' => sub { push @texi2dvi_args, ('-'.$_[0], $_[1]);
push @include_dirs, split(/$quoted_path_separator/, $_[1]); },
'conf-dir=s' => sub { push @conf_dirs, split(/$quoted_path_separator/, $_[1]); },
'P=s' => sub { unshift @prepend_dirs, split(/$quoted_path_separator/, $_[1]); },
'number-sections!' => sub { set_from_cmdline('NUMBER_SECTIONS', $_[1]); },
'number-footnotes!' => sub { set_from_cmdline('NUMBER_FOOTNOTES', $_[1]); },
'node-files!' => sub { set_from_cmdline('NODE_FILES', $_[1]); },
'footnote-style=s' => sub {
my $value = _decode_input($_[1]);
if ($value eq 'end' or $value eq 'separate') {
set_from_cmdline('footnotestyle', $value);
} else {
die _encode_message(
sprintf(__(
"%s: --footnote-style arg must be `separate' or `end', not `%s'."),
$real_command_name, $value))."\n";
}
},
'split=s' => sub { my $split = _decode_input($_[1]);
if (!exists($possible_split{$split})) {
document_warn(
sprintf(__("%s is not a valid split possibility"),
$split));
$split = 'node';
}
set_from_cmdline('SPLIT', $split); },
'no-split' => sub { set_from_cmdline('SPLIT', '');
set_from_cmdline('SPLIT_SIZE', undef);},
'headers!' => sub { set_from_cmdline('HEADERS', $_[1]);
if (!$_[1]) {
set_from_cmdline('FORMAT_MENU', 'nomenu');
} else {
# a special value that is modified below when the
# output format is known, to be the default for that
# format, or 'menu'
set_from_cmdline('FORMAT_MENU',
'set_format_menu_from_cmdline_header_option');
}
if (!$_[1]
and get_conf('TEXINFO_OUTPUT_FORMAT') eq 'info') {
set_main_program_default('TEXINFO_OUTPUT_FORMAT',
'plaintext');
}
},
'output|out|o=s' => sub {
my $var = 'OUTFILE';
if ($_[1] ne '-' and ($_[1] =~ m:/$: or -d $_[1])) {
set_from_cmdline($var, undef);
$var = 'SUBDIR';
}
set_from_cmdline($var, _decode_input($_[1]));
push @texi2dvi_args, '-o', $_[1];
},
'no-validate|no-pointer-validate' => sub {
set_from_cmdline('novalidate',$_[1]);
},
'no-warn' => sub { set_from_cmdline('NO_WARN', $_[1]); },
'verbose|v!' => sub { set_from_cmdline('VERBOSE', $_[1]);
push @texi2dvi_args, '--verbose'; },
'document-language=s' => sub {
my $documentlanguage = _decode_input($_[1]);
my ($messages, $lang, $region)
= Texinfo::Common::warn_unknown_language($documentlanguage);
if (defined($messages)) {
foreach my $message (@$messages) {
document_warn($message);
}
}
if (defined($lang)) {
set_from_cmdline('documentlanguage', $documentlanguage);
}
},
'D=s' => sub {
my $var = $_[1];
my @field = split (/\s+/, $var, 2);
if (scalar(@field) == 1) {
$parser_options->{'values'}->{_decode_input($var)} = 1;
push @texi2dvi_args, "--command=\@set $var 1";
} else {
$parser_options->{'values'}->{_decode_input($field[0])}
= _decode_input($field[1]);
push @texi2dvi_args, "--command=\@set $field[0] $field[1]";
}
},
'U=s' => sub {
delete $parser_options->{'values'}->{_decode_input($_[1])};
push @texi2dvi_args, "--command=\@clear $_[1]";
},
'init-file=s' => sub {
if (get_conf('TEST')) {
locate_and_load_init_file($_[1], [ @conf_dirs ]);
} else {
locate_and_load_init_file($_[1], [ @conf_dirs, @converter_init_dirs ],
\%deprecated_directories);
}
},
'set-customization-variable|c=s' => sub {
my $var_val = $_[1];
# the string is a byte string, so \w should correspond to ASCII
# characters only, which is good
if ($var_val =~ s/^(\w+)\s*=?\s*//) {
my $var = $1;
my $value = $var_val;
if ($var eq 'TEXINFO_OUTPUT_FORMAT') {
$value = _decode_input($var_val);
# TODO verify that it is the best. It is inconsistent with other
# customization options that have the same precedence as command
# line option when specified on the command line. This is because
# in the manual, it is said:
# "The customization variable of the same name is also read; if set,
# that overrides an environment variable setting, but not a command-line
# option."
# If read means "read from an init file" then we could change here, but
# if it means "read from the command line or an init file" we should
# keep it as it is.
set_format($value);
} else {
if ($value =~ /^undef$/i) {
$value = undef;
} elsif (!exists(
$Texinfo::Common::non_decoded_customization_variables{$var})) {
$value = _decode_input($var_val);
}
set_from_cmdline($var, $value);
}
}
},
'css-include=s' => \@css_files,
'css-ref=s' => \@css_refs,
'transliterate-file-names!' =>
sub {set_from_cmdline('TRANSLITERATE_FILE_NAMES', $_[1]);},
'error-limit|e=i' => sub { set_from_cmdline('ERROR_LIMIT', $_[1]); },
'split-size=s' => sub {set_from_cmdline('SPLIT_SIZE', $_[1])},
'paragraph-indent|p=s' => sub {
my $value = _decode_input($_[1]);
if ($value =~ /^([0-9]+)$/ or $value eq 'none' or $value eq 'asis') {
set_from_cmdline('paragraphindent', $value);
} else {
die _encode_message(sprintf(
__("%s: --paragraph-indent arg must be numeric/`none'/`asis', not `%s'."),
$real_command_name, $value)."\n");
}
},
'fill-column|f=i' => sub {set_from_cmdline('FILLCOLUMN',$_[1]);},
'enable-encoding' => sub {set_from_cmdline('ENABLE_ENCODING',$_[1]);
},
'disable-encoding' => sub {set_from_cmdline('ENABLE_ENCODING', 0);
},
'trace-includes' => sub {set_from_cmdline('TRACE_INCLUDES', 1);},
'internal-links=s' => sub {set_from_cmdline('INTERNAL_LINKS', $_[1]);},
'force|F' => sub {set_from_cmdline('FORCE', $_[1]);},
'commands-in-node-names' => sub { ;},
'output-indent=i' => sub { ;},
'reference-limit=i' => sub { ;},
'Xopt=s' => sub {push @texi2dvi_args, $_[1]; $Xopt_arg_nr++},
'silent|quiet' => sub { push @texi2dvi_args, '--'.$_[0];},
'plaintext' => sub {set_cmdline_format($_[0].'');},
'html' => sub {set_cmdline_format($_[0].'');},
'epub3' => sub {set_cmdline_format($_[0].'');},
'latex' => sub {set_cmdline_format($_[0].'');},
'info' => sub {set_cmdline_format($_[0].'');},
'docbook' => sub {set_cmdline_format($_[0].'');},
'dvi' => sub {set_cmdline_format($_[0].'');},
'dvipdf' => sub {set_cmdline_format($_[0].'');},
'ps' => sub {set_cmdline_format($_[0].'');},
'pdf' => sub {set_cmdline_format($_[0].'');},
'debug=i' => sub {set_from_cmdline('DEBUG', $_[1]);
push @texi2dvi_args, '--'.$_[0]; },
);
exit 1 if (!$result_options);
# those are strings combined with output so decode
my $ref_css_refs = get_conf('CSS_REFS');
my @input_css_refs = @{$ref_css_refs};
@$ref_css_refs = map {_decode_input($_)} @input_css_refs;
# reset translations encoding if COMMAND_LINE_ENCODING was reset
$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);
}
# Change some options depending on the settings of other ones set formats
sub process_config($) {
my $conf = shift;
if ($conf->{'TEXI2HTML'}) {
set_format('html');
$parser_options->{'values'}->{'texi2html'} = 1;
}
}
process_config($cmdline_options);
my $latex2html_file = 'latex2html.pm';
my $html_math_option = get_conf('HTML_MATH');
if (defined($html_math_option) and $html_math_option eq 'l2h') {
locate_and_load_extension_file($latex2html_file, $internal_extension_dirs);
}
my $tex4ht_file = 'tex4ht.pm';
if (defined($html_math_option) and $html_math_option eq 't4h') {
locate_and_load_extension_file($tex4ht_file, $internal_extension_dirs);
}
my $highlight_syntax_file = 'highlight_syntax.pm';
my $highlight_syntax_option = get_conf('HIGHLIGHT_SYNTAX');
if (defined($highlight_syntax_option) and $highlight_syntax_option ne '') {
locate_and_load_extension_file($highlight_syntax_file,
$internal_extension_dirs);
}
# For tests, set some strings to values not changing with releases
my %test_conf = (
'PACKAGE_VERSION' => '',
'PACKAGE' => 'texinfo',
'PACKAGE_NAME' => 'GNU Texinfo',
'PACKAGE_AND_VERSION' => 'texinfo',
'PACKAGE_URL' => 'https://www.gnu.org/software/texinfo/',
# maybe don't set this?
'PROGRAM' => 'texi2any',
);
if (get_conf('TEST')) {
foreach my $conf (keys(%test_conf)) {
$main_program_default_options->{$conf} = $test_conf{$conf};
}
}
my @input_file_suffixes = ('.txi','.texinfo','.texi','.txinfo','');
my %format_names = (
'info' => 'Info',
'html' => 'HTML',
'docbook' => 'DocBook',
'epub3' => 'EPUB 3',
'plaintext' => 'Plain Text',
'texinfoxml' => 'Texinfo XML',
);
sub name_of_format($) {
my $format = shift;
if (exists($format_names{$format})) {
return $format_names{$format};
} else {
return $format;
}
}
my $init_file_format = Texinfo::Config::GNUT_get_format_from_init_file();
if (defined($init_file_format)) {
set_format($init_file_format);
}
if (defined($ENV{'TEXINFO_OUTPUT_FORMAT'})
and $ENV{'TEXINFO_OUTPUT_FORMAT'} ne '') {
set_format(_decode_input($ENV{'TEXINFO_OUTPUT_FORMAT'}));
}
my $output_format = get_conf('TEXINFO_OUTPUT_FORMAT');
sub _exit($$) {
my ($error_count, $opened_files) = @_;
if ($error_count and defined($opened_files) and !get_conf('FORCE')) {
foreach my $opened_file (keys(%$opened_files)) {
unlink ($opened_file);
delete $opened_files->{$opened_file};
}
}
exit (1) if ($error_count and (!get_conf('FORCE')
or $error_count > get_conf('ERROR_LIMIT')));
}
sub _output_error_messages($) {
my $errors = shift;
foreach my $error_message (@$errors) {
if ($error_message->{'type'} eq 'error' or !get_conf('NO_WARN')) {
my $s = '';
if (defined($error_message->{'file_name'})) {
my $file = $error_message->{'file_name'};
if (get_conf('TEST')) {
# otherwise out of source build fails since the file names
# are different
my ($directories, $suffix);
($file, $directories, $suffix) = fileparse($file);
}
$s .= "$file:";
}
if (defined($error_message->{'line_nr'})) {
$s .= $error_message->{'line_nr'} . ':';
}
$s .= ' ' if ($s ne '');
$s .= _encode_message($error_message->{'error_line'});
warn $s;
}
}
}
sub handle_errors($$$) {
my ($errors, $error_count, $opened_files) = @_;
$error_count += Texinfo::Report::count_errors($errors);
_output_error_messages($errors);
_exit($error_count, $opened_files);
return $error_count;
}
# If the file overwriting becomes an error, should increase $ERROR_COUNT.
sub merge_opened_files($$$)
{
my ($error_count, $opened_files, $newly_opened_files) = @_;
if (defined($newly_opened_files)) {
foreach my $opened_file (sort(keys(%$newly_opened_files))) {
# NOTE paths are not normalized, therefore different paths names
# that refers to the same file will not trigger the message.
if (exists($opened_files->{$opened_file})) {
document_warn(sprintf(__('overwriting file: %s'),
$opened_file));
} else {
$opened_files->{$opened_file} = 1;
}
}
}
return $error_count;
}
# Avoid loading these modules until down here to speed up the case
# when they are not needed.
require Texinfo::Parser;
require Texinfo::Translations;
require Texinfo::Document;
Texinfo::Document->import();
require Texinfo::Structuring;
Texinfo::Structuring->import();
require Texinfo::Transformations;
Texinfo::Transformations->import();
require Texinfo::Convert::Utils;
Texinfo::Convert::Utils->import();
if (not get_conf('TEST') and $Texinfo::ModulePath::texinfo_uninstalled) {
push @texinfo_language_config_dirs,
join('/', ($Texinfo::ModulePath::t2a_srcdir, $updir, 'util'));
}
if ($Texinfo::ModulePath::texinfo_uninstalled) {
my $locales_dir
= join('/', ($Texinfo::ModulePath::t2a_builddir, 'LocaleData'));
if (-d $locales_dir) {
Texinfo::Translations::setup_output_strings($locales_dir,
$strings_textdomain);
} else {
warn "Locales dir for document strings not found\n";
}
} else {
my $locales_dir = join('/', ($datadir, 'locale'));
Texinfo::Translations::setup_output_strings($locales_dir,
$strings_textdomain);
}
my %tree_transformations;
if (get_conf('TREE_TRANSFORMATIONS')) {
my @transformations = split /,/, get_conf('TREE_TRANSFORMATIONS');
foreach my $transformation (@transformations) {
if (Texinfo::Common::valid_tree_transformation($transformation)) {
$tree_transformations{$transformation} = 1;
} else {
document_warn(sprintf(__('unknown tree transformation %s'),
$transformation));
}
}
}
# for a format setup with an init file
if (exists($formats_table{$output_format}->{'init_file'})) {
locate_and_load_extension_file($formats_table{$output_format}->{'init_file'},
$internal_extension_dirs);
}
# in general the format name is the format being converted. If this is
# not the case, the converted format is set here. For example, for
# the epub3 format, the converted format is html. The converted format
# should be the format actually used for conversion code, in practice
# this means that the module associated with the converted format in
# $formats_table will be used to find the converter methods.
my $converted_format = $output_format;
if (exists($formats_table{$output_format}->{'converted_format'})) {
$converted_format = $formats_table{$output_format}->{'converted_format'};
}
if (get_conf('SPLIT') and !$formats_table{$converted_format}->{'split'}) {
if ($converted_format ne $output_format) {
document_warn(sprintf(
__('ignoring splitting for converted format %s (for %s)'),
name_of_format($converted_format),
name_of_format($output_format)));
} else {
document_warn(sprintf(__('ignoring splitting for format %s'),
name_of_format($converted_format)));
}
set_from_cmdline('SPLIT', '');
}
my $default_expanded_formats = _format_expanded_formats($output_format);
foreach my $ignored_format (keys(%ignored_formats)) {
delete $default_expanded_formats->{$ignored_format};
}
add_to_option_list('EXPANDED_FORMATS', [sort(keys(%$default_expanded_formats))]);
if ($call_texi2dvi) {
if (defined(get_conf('OUTFILE')) and @ARGV > 1) {
die _encode_message(
sprintf(
__('%s: when generating %s, only one input FILE may be specified with -o')."\n",
$real_command_name, name_of_format($output_format)));
}
} elsif($Xopt_arg_nr) {
document_warn(__('--Xopt option without printed output'));
}
# special case, show all the built in HTML CSS rules and exit
if (get_conf('SHOW_BUILTIN_CSS_RULES')) {
require Texinfo::Convert::HTML;
print STDOUT Texinfo::Convert::HTML::builtin_default_css_text();
exit(0);
}
if (exists($formats_table{$converted_format}->{'module'})) {
# Speed up initialization by only loading the module we need.
my $module = $formats_table{$converted_format}->{'module'};
eval "require $module";
my $error = $@;
if ($error ne '') {
die _encode_message(sprintf(__("error loading %s: %s"), $module, $error));
};
eval "$module->import;";
eval '$formats_table{$converted_format}->{\'converter\'} = sub{'.
$formats_table{$converted_format}->{'module'}
.'->converter(@_)};';
}
# For now, FORMAT_MENU is the only variable that can be set from converter
# defaults for the main program structuring and for the parser.
# This could be done for more variables if
# converter default becomes relevant for more variables, either
# for the parser or the main program.
# Specific variable for 'FORMAT_MENU' to keep the converter information
# even if the command line higher precedence option is set in case
# command line is set_format_menu_from_cmdline_header_option.
my $conversion_format_menu_default;
if (exists($formats_table{$converted_format}->{'module'})) {
my $converter_class = $formats_table{$converted_format}->{'module'};
# $cmdline_options is passed to have command line settings, here
# in practice TEXI2HTML set, for conversion to HTML to select
# possibly different customization variable values.
my $converter_defaults
= $converter_class->converter_defaults($cmdline_options);
# some converters may return undef for $converter_defaults (Text for example)
if ($converter_defaults
and defined($converter_defaults->{'FORMAT_MENU'})) {
# could be done for other customization options
set_main_program_default('FORMAT_MENU',
$converter_defaults->{'FORMAT_MENU'});
# for FORMAT_MENU need in addition to have the value if
# command-line set to 'set_format_menu_from_cmdline_header_option'
$conversion_format_menu_default = $converter_defaults->{'FORMAT_MENU'};
} else {
# this happens for the plaintexinfo format for which nothing
# is set.
}
}
# special case for FORMAT_MENU of delayed setting based in
# some case on converter
if (defined(get_conf('FORMAT_MENU'))
and get_conf('FORMAT_MENU')
eq 'set_format_menu_from_cmdline_header_option') {
# set FORMAT_MENU to the output format default, if not nomenu
if (defined($conversion_format_menu_default)
and $conversion_format_menu_default ne 'nomenu') {
set_from_cmdline('FORMAT_MENU', $conversion_format_menu_default);
} else {
set_from_cmdline('FORMAT_MENU', 'menu');
}
}
# using no warnings is wrong, but a way to avoid a spurious warning.
no warnings 'once';
my @parser_settable_options
= keys(%Texinfo::Common::default_parser_customization_values);
# Copy relevant customization variables into the parser options.
foreach my $parser_settable_option (@parser_settable_options) {
if (defined(get_conf($parser_settable_option))) {
$parser_options->{$parser_settable_option}
= get_conf($parser_settable_option);
}
}
# Main processing, process all the files given on the command line
# Note that the input file names are binary strings and are not decoded
my @input_files = @ARGV;
# use STDIN if not a tty
@input_files = ('-') if (!scalar(@input_files) and !-t STDIN
and !$call_texi2dvi);
my $input_files_nr = scalar(@input_files);
die _encode_message(
sprintf(__("%s: missing file argument.")."\n", $real_command_name)
.sprintf(__("Try `%s --help' for more information.")."\n", $real_command_name))
unless ($input_files_nr >= 1);
# XS parser and not explicitly unset
my $XS_structuring = Texinfo::XSLoader::XS_structuring_enabled();
if (defined($ENV{TEXINFO_XS_EXTERNAL_CONVERSION})
and $ENV{TEXINFO_XS_EXTERNAL_CONVERSION}) {
set_from_cmdline('XS_EXTERNAL_CONVERSION', 1);
}
if (defined($ENV{TEXINFO_XS_EXTERNAL_FORMATTING})
and $ENV{TEXINFO_XS_EXTERNAL_FORMATTING}) {
set_from_cmdline('XS_EXTERNAL_FORMATTING', 1);
}
my $remove_references = 0;
my $test_level = get_conf('TEST');
if (defined($test_level)) {
$remove_references = 1 if ($test_level > 1);
# in a test, /dev/null is used as out file. Replace by the platform
# null device if it is not /dev/null.
my $outfile = get_conf('OUTFILE');
if (defined($outfile) and $outfile eq '/dev/null'
and scalar(keys(%Texinfo::Common::null_device_file))
and !exists($Texinfo::Common::null_device_file{$outfile})) {
my @null_devices = keys(%Texinfo::Common::null_device_file);
set_from_cmdline('OUTFILE', $null_devices[0]);
}
}
my $file_index = -1;
my %opened_files;
my %main_unclosed_files;
my $error_count = 0;
# main processing
while (@input_files) {
$file_index++;
my $input_file_arg = shift(@input_files);
my $input_file_name;
if ($input_file_arg =~ /\.info$/) {
my $basename = $input_file_arg;
$basename =~ s|.*/||;
my $corrected = $basename;
$corrected =~ s/\.info$/.texi/;
document_warn(sprintf(
__('input file %s; did you mean %s?'), _decode_input($basename),
_decode_input($corrected)));
}
# try to concatenate with different suffixes. The last suffix is ''
# such that the plain file name is checked.
foreach my $suffix (@input_file_suffixes) {
if (-e $input_file_arg.$suffix) {
$input_file_name = $input_file_arg.$suffix;
last;
}
}
# in case no file was found, still set the file name
$input_file_name = $input_file_arg if (!defined($input_file_name));
my ($input_filename, $input_directory, $suffix) = fileparse($input_file_name);
my $canon_input_dir;
if (!defined($input_directory) or $input_directory eq '') {
$input_directory = $curdir;
$canon_input_dir = $curdir;
} else {
$canon_input_dir
= Texinfo::Common::file_separator_canonpath($input_directory);
}
#my $input_file_base = $input_file_name;
#$input_file_base =~ s/\.te?x(i|info)?$//;
# a shallow copy is not sufficient for arrays and hashes to make
# sure that the $parser_options are not modified if $parser_file_options
# are modified
my $parser_file_options = Storable::dclone($parser_options);
my @prepended_include_directories = ($curdir);
push @prepended_include_directories, $input_directory
if ($canon_input_dir ne $curdir);
@prepended_include_directories =
(@prepend_dirs, @prepended_include_directories);
unshift @{$parser_file_options->{'INCLUDE_DIRECTORIES'}},
@prepended_include_directories;
my $parser = Texinfo::Parser::parser($parser_file_options);
my $document = $parser->parse_texi_file($input_file_name);
# setup a configuration Perl object which defines get_conf and set_conf,
# use the main program customization information with per-document
# customization. This allows to use functions calling get_conf and
# set_conf to manipulate customization information.
# After this is done, the customization information should not
# change enymore, and it is registered in the document and used by
# Structuring/Transformations methods needing access to configuration
# information.
#
# OUTPUT_ENCODING_NAME is set in set_output_encoding and accessed
# in output_files_open_out.
# The following variables are used in Structuring/Transformations:
# novalidate, FORMAT_MENU, CHECK_NORMAL_MENU_STRUCTURE,
# CHECK_MISSING_MENU_ENTRY. And DEBUG.
# documentlanguage and documentscript are used in
# Structuring/Transformations for gdt() in regenerate_master_menu
my $main_configuration = Texinfo::MainConfig::new();
# ALTIMP tta/C/main/document.c set_document_options
my $document_information = $document->global_information();
# encoding is needed for output files
Texinfo::Common::set_output_encoding($main_configuration, $document);
# relevant for many Structuring methods.
if ($document_information->{'novalidate'}) {
$main_configuration->set_conf('novalidate', 1);
}
# Now that all the configuration has been set, register with the
# document
my $document_options = $main_configuration->get_customization_options_hash();
$document->register_document_options($document_options);
# Get the tree object. Note that if XS structuring in on, the argument
# prevents the tree being built as a Perl structure at this stage; only
# a "handle" is returned.
my $tree = $document->tree($XS_structuring);
if (defined($tree)
and (get_conf('DUMP_TREE')
or (get_conf('DEBUG') and get_conf('DEBUG') >= 10))) {
my $tree = $document->tree();
my $input_file_names_encoding
= Texinfo::Common::input_file_name_encoding(
get_conf('INPUT_FILE_NAME_ENCODING'),
get_conf('DOC_ENCODING_FOR_INPUT_FILE_NAME'),
get_conf('LOCALE_ENCODING'), $document);
my $printed_tree
= Texinfo::ManipulateTree::tree_print_details($tree,
$input_file_names_encoding, $test_level);
my $output_encoding = $document->get_conf('OUTPUT_ENCODING_NAME');
if (defined($output_encoding)) {
$printed_tree = Encode::encode($output_encoding, $printed_tree);
if ($output_encoding ne 'utf-8') {
$printed_tree = "OUTPUT_ENCODING: $output_encoding\n" .$printed_tree;
}
}
my $dump_tree = get_conf('DUMP_TREE');
if (!defined($dump_tree) or $dump_tree eq '-') {
print STDERR $printed_tree;
} else {
my $dump_tree_name = _decode_input($dump_tree);
if (!open(FH, '>', $dump_tree)) {
document_warn(sprintf(__("could not open %s for writing: %s"),
$dump_tree_name, $!));
} else {
print FH $printed_tree;
if (!close(FH)) {
document_warn(sprintf(__("error on closing tree dump file %s: %s"),
$dump_tree_name, $!));
}
}
}
## this is very wrong, but a way to avoid a spurious warning.
#no warnings 'once';
#local $Data::Dumper::Purity = 1;
#no warnings 'once';
#local $Data::Dumper::Indent = 1;
#no warnings 'once';
#local $Data::Dumper::Sortkeys = 1;
#print STDERR Data::Dumper->Dump([$tree]);
}
if (!defined($tree)) {
$error_count
= handle_errors($document->parser_errors(), $error_count,
\%opened_files);
goto NEXT;
}
if ($output_format eq 'parse') {
$error_count
= handle_errors($document->parser_errors(), $error_count,
\%opened_files);
goto NEXT;
}
if (get_conf('TRACE_INCLUDES')) {
$error_count
= handle_errors($document->parser_errors(), $error_count,
\%opened_files);
my $included_file_paths = $document_information->{'included_files'};
if (defined($included_file_paths)) {
foreach my $included_file (@$included_file_paths) {
print STDOUT "$included_file\n";
}
}
goto NEXT;
}
if (defined(get_conf('MACRO_EXPAND')) and $file_index == 0) {
require Texinfo::Convert::Texinfo;
Texinfo::Convert::Texinfo->import();
# if convert_to_texinfo is not XS code get Perl tree.
if (not Texinfo::XSLoader::XS_convert_enabled()) {
$tree = $document->tree();
}
my $texinfo_text = Texinfo::Convert::Texinfo::convert_to_texinfo($tree);
#print STDERR "$texinfo_text\n";
my $encoded_macro_expand_file_name = get_conf('MACRO_EXPAND');
my $macro_expand_file_name = _decode_input($encoded_macro_expand_file_name);
my $macro_expand_files_information
= Texinfo::Convert::Utils::output_files_initialize();
# the third return information, set if the file has already been used
# in this files_information is not checked as this cannot happen.
my ($macro_expand_fh, $error_message)
= Texinfo::Convert::Utils::output_files_open_out(
$macro_expand_files_information,
$encoded_macro_expand_file_name, undef,
$document->get_conf('OUTPUT_ENCODING_NAME'));
my $error_macro_expand_file;
if (defined($macro_expand_fh)) {
print $macro_expand_fh $texinfo_text;
if ($macro_expand_file_name ne '-') {
if (!close($macro_expand_fh)) {
document_warn(sprintf(__("error on closing macro expand file %s: %s"),
$macro_expand_file_name, $!));
$error_macro_expand_file = 1;
}
Texinfo::Convert::Utils::output_files_register_closed(
$macro_expand_files_information,
$encoded_macro_expand_file_name);
} else {
$main_unclosed_files{$macro_expand_file_name} = $macro_expand_fh;
}
} else {
document_warn(sprintf(__("could not open %s for writing: %s"),
$macro_expand_file_name, $error_message));
$error_macro_expand_file = 1;
}
my $macro_expand_opened_file =
Texinfo::Convert::Utils::output_files_opened_files(
$macro_expand_files_information);
$error_macro_expand_file
= merge_opened_files($error_macro_expand_file, \%opened_files,
$macro_expand_opened_file);
# we do not need to go through unclosed files of
# $macro_expand_files_information as we know that the file is
# already closed if needed, or registered in main_unclosed_files.
if ($error_macro_expand_file) {
$error_count++;
_exit($error_count, \%opened_files);
}
}
if (get_conf('DUMP_TEXI')
or exists($formats_table{$output_format}->{'texi2dvi_format'})) {
$error_count = handle_errors($document->parser_errors(), $error_count,
\%opened_files);
goto NEXT;
}
if (!$XS_structuring) {
# rebuild every information associated to the document, such that
# no flags of modified structure in C are set and XS code called
# to get document structures directly returns the Perl data.
$tree = Texinfo::Document::build_tree($tree);
}
if ($tree_transformations{'fill_gaps_in_sectioning'}) {
Texinfo::Transformations::fill_gaps_in_sectioning_in_document($document);
}
if (exists($formats_table{$converted_format}->{'relate_index_entries_to_table_items'})
or $tree_transformations{'relate_index_entries_to_table_items'}) {
Texinfo::ManipulateTree::relate_index_entries_to_table_items_in_document(
$document);
}
if (exists($formats_table{$converted_format}->{'move_index_entries_after_items'})
or $tree_transformations{'move_index_entries_after_items'}) {
Texinfo::ManipulateTree::move_index_entries_after_items_in_document(
$document);
}
if ($tree_transformations{'insert_nodes_for_sectioning_commands'}) {
Texinfo::Transformations::insert_nodes_for_sectioning_commands($document);
}
Texinfo::Structuring::associate_internal_references($document);
# information obtained through Texinfo::Structuring
# and useful in converters.
# every format needs the sectioning structure
Texinfo::Structuring::sectioning_structure($document);
if (!exists($formats_table{$converted_format}->{'no_warn_non_empty_parts'})) {
Texinfo::Structuring::warn_non_empty_parts($document);
}
if ($tree_transformations{'complete_tree_nodes_menus'}) {
Texinfo::Transformations::complete_tree_nodes_menus_in_document($document);
} elsif ($tree_transformations{'complete_tree_nodes_missing_menu'}) {
Texinfo::Transformations::complete_tree_nodes_missing_menu($document);
}
if ($tree_transformations{'regenerate_master_menu'}) {
Texinfo::Transformations::regenerate_master_menu($document);
}
if (exists($formats_table{$converted_format}->{'nodes_tree'})) {
Texinfo::Structuring::construct_nodes_tree($document);
# With this condition, menu is the default for 'FORMAT_MENU'.
# However, this can only happen if
# 1) 'nodes_tree' is set for the converted format,
# 2) there is a 'module' setting a converter, such that the default set
# in Texinfo::Common is not used,
# 3) FORMAT_MENU is not set in the converter defaults.
# In practice these three conditions are never met, so the default
# is never used.
if (not defined(get_conf('FORMAT_MENU'))
or get_conf('FORMAT_MENU') eq 'menu') {
Texinfo::Structuring::check_node_tree_menu_structure($document);
Texinfo::Structuring::complete_node_tree_with_menus($document);
Texinfo::Structuring::check_nodes_are_referenced($document);
}
}
if (exists($formats_table{$converted_format}->{'floats'})) {
Texinfo::Structuring::number_floats($document);
}
# a shallow copy is not sufficient for arrays and hashes to make
# sure that the $cmdline_options are not modified if $file_cmdline_options
# are modified
my $file_cmdline_options = Storable::dclone($cmdline_options);
if ($file_index != 0) {
delete $file_cmdline_options->{'OUTFILE'};
delete $file_cmdline_options->{'PREFIX'};
delete $file_cmdline_options->{'SUBDIR'} if (get_conf('SPLIT'));
}
my $converter;
# setup the converter here in case translation are customized
# such that the converter can be in argument of
# Texinfo::Document::setup_indices_sort_strings
if (exists($formats_table{$converted_format}->{'converter'})) {
# the code in Texinfo::Config makes sure that the keys appear only
# once in these three hashes.
my $converter_options = { %$main_program_default_options,
%$init_files_options,
%$file_cmdline_options,
};
# NOTE nothing set through $main_configuration in customization registered in
# document is passed directly, which is clean, the Converters already
# have that information in $converter_options, can determine it themselves
# or use their defaults.
# It could be possible to pass some information if it allows
# for instance to have some consistent information for Structuring
# and Converters.
$converter_options->{'deprecated_config_directories'}
= \%deprecated_directories;
unshift @{$converter_options->{'INCLUDE_DIRECTORIES'}},
@prepended_include_directories;
my @prepended_texinfo_language_directories = ($curdir);
push @prepended_texinfo_language_directories, $input_directory
if ($canon_input_dir ne $curdir);
unshift @{$converter_options->{'TEXINFO_LANGUAGE_DIRECTORIES'}},
@prepended_texinfo_language_directories;
$converter = &{$formats_table{$converted_format}
->{'converter'}}($converter_options);
}
# do it now to get error messages here
#if (exists($formats_table{$converted_format}->{'setup_index_entries_sort_strings'})) {
# Texinfo::Document::setup_indices_sort_strings($document, $converter);
#}
#Texinfo::Document::rebuild_document($document);
# parser errors
my $errors = $document->parser_errors();
# document/structuring errors
my $document_errors = $document->errors();
push @$errors, @$document_errors;
$error_count = handle_errors($errors,
$error_count, \%opened_files);
if (get_conf('DUMP_STRUCTURE')
or (get_conf('DEBUG') and get_conf('DEBUG') >= 20)) {
my $tree = $document->tree();
my $input_file_names_encoding
= Texinfo::Common::input_file_name_encoding(
get_conf('INPUT_FILE_NAME_ENCODING'),
get_conf('DOC_ENCODING_FOR_INPUT_FILE_NAME'),
get_conf('LOCALE_ENCODING'), $document);
my $printed_tree
= Texinfo::ManipulateTree::tree_print_details($tree,
$input_file_names_encoding, $test_level);
my $output_encoding = $document->get_conf('OUTPUT_ENCODING_NAME');
if (defined($output_encoding)) {
$printed_tree = Encode::encode($output_encoding, $printed_tree);
if ($output_encoding ne 'utf-8') {
$printed_tree = "OUTPUT_ENCODING: $output_encoding\n" .$printed_tree;
}
}
my $dump_structure = get_conf('DUMP_STRUCTURE');
if (!defined($dump_structure) or $dump_structure eq '-') {
print STDERR $printed_tree;
} else {
my $dump_structure_name = _decode_input($dump_structure);
if (!open(FH, '>', $dump_structure)) {
document_warn(sprintf(__("could not open %s for writing: %s"),
$dump_structure_name, $!));
} else {
print FH $printed_tree;
if (!close(FH)) {
document_warn(
sprintf(__("error on closing structure dump file %s: %s"),
$dump_structure_name, $!));
}
}
}
}
if ($output_format eq 'structure') {
goto NEXT;
}
$converter->output($document);
my $converter_errors = $converter->get_converter_errors();
my $converter_opened_files
= Texinfo::Convert::Utils::output_files_opened_files(
$converter->output_files_information());
$error_count = merge_opened_files($error_count, \%opened_files,
$converter_opened_files);
$error_count = handle_errors($converter_errors,
$error_count, \%opened_files);
my $converter_unclosed_files
= Texinfo::Convert::Utils::output_files_unclosed_files(
$converter->output_files_information());
if (defined($converter_unclosed_files)) {
foreach my $unclosed_file (keys(%$converter_unclosed_files)) {
my $fh = $converter_unclosed_files->{$unclosed_file};
# undefined file handle means that the path comes from XS (normally
# through build_output_files_unclosed_files) but is not associated
# with a file handle yet, as a file handle can't be directly associated
# with a stream in C code, but the stream can be returned through
# an XS interface, here
# Texinfo::Convert::ConvertConverterXS::get_unclosed_stream.
if (!defined($fh)) {
$fh = $converter->XS_get_unclosed_stream($unclosed_file);
if (!defined($fh)) {
next;
}
}
if ($unclosed_file eq '-') {
$main_unclosed_files{$unclosed_file} = $fh;
} else {
if (!close($fh)) {
warn(sprintf(__("%s: error on closing %s: %s")."\n",
$real_command_name, $unclosed_file, $!));
$error_count++;
_exit($error_count, \%opened_files);
}
}
}
}
# Reset now that we got the information. Could be important if we reused
# the converter.
Texinfo::Convert::Utils::output_files_reset(
$converter->output_files_information());
if (defined(get_conf('INTERNAL_LINKS')) and $file_index == 0
and exists($formats_table{$converted_format}->{'internal_links'})) {
my $internal_links_text = $converter->output_internal_links();
# always create a file, even if empty.
$internal_links_text = '' if (!defined($internal_links_text));
my $encoded_internal_links_file_name = get_conf('INTERNAL_LINKS');
my $internal_links_file_name
= _decode_input($encoded_internal_links_file_name);
my $internal_links_files_information
= Texinfo::Convert::Utils::output_files_initialize();
# the third return information, set if the file has already been used
# in this files_information is not checked as this cannot happen.
my ($internal_links_fh, $error_message)
= Texinfo::Convert::Utils::output_files_open_out(
$internal_links_files_information,
$encoded_internal_links_file_name, undef,
$converter->get_conf('OUTPUT_ENCODING_NAME'));
my $error_internal_links_file;
if (defined($internal_links_fh)) {
print $internal_links_fh $internal_links_text;
if ($internal_links_file_name ne '-') {
if (!close($internal_links_fh)) {
warn(sprintf(__("%s: error on closing internal links file %s: %s")."\n",
$real_command_name, $internal_links_file_name, $!));
$error_internal_links_file = 1;
}
Texinfo::Convert::Utils::output_files_register_closed(
$internal_links_files_information,
$encoded_internal_links_file_name);
} else {
$main_unclosed_files{$internal_links_file_name} = $internal_links_fh;
}
} else {
warn(sprintf(__("%s: could not open %s for writing: %s")."\n",
$real_command_name, $internal_links_file_name,
$error_message));
$error_internal_links_file = 1;
}
my $internal_links_opened_file
= Texinfo::Convert::Utils::output_files_opened_files(
$internal_links_files_information);
$error_internal_links_file
= merge_opened_files($error_internal_links_file,
\%opened_files, $internal_links_opened_file);
# we do not need to go through unclosed files of
# $internal_links_files_information as we know that the file is
# already closed if needed, or registered in main_unclosed_files.
if ($error_internal_links_file) {
$error_count++;
_exit($error_count, \%opened_files);
}
}
# To debug the state of the output units lists after the destruction of
# the converter, get output units lists before they are destroyed.
# This adds a reference, so the problem should be diagnosed
# before enabling this code. This is mainly relevant with XS,
# see below.
#my @output_units_lists;
#if (defined($test_level) and $test_level > 1) {
# @output_units_lists = $converter->XS_get_output_units_lists();
#}
# Texinfo::Converter::Text does not define it.
# If the program is about to exit, all the memory will be released,
# so we only cleanup at all if TEST is set.
if (($test_level or $file_index < $input_files_nr -1)
and $converter->can('converter_remove_output_units')) {
$converter->converter_remove_output_units();
}
# When the output units are created from XS, they are not registered
# in the Converter output units lists, so there is no place in the Perl code
# where the output units can be accessed easily to have Devel::FindRef::track
# called on them, although this is very handy if, with TEST > 1, the
# XS/C code showed remaining references when checking refcounts.
#
# It is possible to call Devel::FindRef::track here, for that
# the output_units_lists need to have been gathered before. Then it is
# possible to go through them:
#if (defined($test_level) and $test_level > 1) {
# my $index = -1;
# foreach my $output_unit_list (@output_units_lists) {
# $index++;
# next unless(defined($output_unit_list));
# print STDERR "OUTPUT UNITS LIST $index $output_unit_list\n";
# foreach my $output_unit (@$output_unit_list) {
# print STDERR "FROM XS $output_unit ".Devel::Peek::SvREFCNT($output_unit).
# " HV: ".Devel::Refcount::refcount($output_unit)."\n"
# .Devel::FindRef::track($output_unit)."\n";
# }
# }
#}
if (defined(get_conf('SORT_ELEMENT_COUNT')) and $file_index == 0) {
require Texinfo::Convert::TextContent;
my $sort_element_converter_options = { %$main_program_default_options,
%$init_files_options,
%$file_cmdline_options,
};
unshift @{$sort_element_converter_options->{'INCLUDE_DIRECTORIES'}},
@prepended_include_directories;
my $converter_element_count
= Texinfo::Convert::TextContent->converter(
$sort_element_converter_options);
# here could be $output_format or $converted_format.
# Since $converted_format is used above for ->{'nodes_tree'}, use it
# here again.
my $use_sections
= (! exists($formats_table{$converted_format}->{'nodes_tree'})
or (defined($converter_element_count->get_conf('USE_NODES'))
and !$converter_element_count->get_conf('USE_NODES')));
my ($sorted_name_counts_array, $sort_element_count_text)
= $converter_element_count->sort_element_counts($document,
$use_sections, get_conf('SORT_ELEMENT_COUNT_WORDS'));
my $sort_element_count_file_name = get_conf('SORT_ELEMENT_COUNT');
my ($encoded_sort_element_count_file_name, $path_encoding)
= $converter_element_count->encoded_output_file_name(
$sort_element_count_file_name);
my $sort_elem_files_information
= Texinfo::Convert::Utils::output_files_initialize();
# the third return information, set if the file has already been used
# in this files_information is not checked as this cannot happen.
my ($sort_element_count_fh, $error_message)
= Texinfo::Convert::Utils::output_files_open_out(
$sort_elem_files_information,
$encoded_sort_element_count_file_name, undef,
$converter_element_count->get_conf('OUTPUT_ENCODING_NAME'));
my $error_sort_element_count_file;
if (defined($sort_element_count_fh)) {
print $sort_element_count_fh $sort_element_count_text;
if ($sort_element_count_file_name ne '-') {
if (!close($sort_element_count_fh)) {
warn(sprintf(__("%s: error on closing elements size file %s: %s")."\n",
$real_command_name, $sort_element_count_file_name, $!));
$error_sort_element_count_file = 1;
}
Texinfo::Convert::Utils::output_files_register_closed(
$sort_elem_files_information,
$encoded_sort_element_count_file_name);
} else {
$main_unclosed_files{$sort_element_count_file_name}
= $sort_element_count_fh;
}
} else {
warn(sprintf(__("%s: could not open %s for writing: %s")."\n",
$real_command_name, $sort_element_count_file_name, $!));
$error_sort_element_count_file = 1;
}
my $sort_element_count_file_opened_file
= Texinfo::Convert::Utils::output_files_opened_files(
$sort_elem_files_information);
$error_sort_element_count_file
= merge_opened_files($error_sort_element_count_file,
\%opened_files, $sort_element_count_file_opened_file);
$converter_element_count->converter_remove_output_units();
$converter_element_count->destroy_converter();
# we do not need to go through unclosed files of
# $sort_elem_files_information as we know that the file is
# already closed if needed, or registered in main_unclosed_files.
if ($error_sort_element_count_file) {
$error_count++;
_exit($error_count, \%opened_files);
}
}
if ($test_level or $file_index < $input_files_nr -1) {
$converter->destroy_converter();
}
NEXT:
$parser->release();
$parser = undef;
if ($test_level or $file_index < $input_files_nr -1) {
# Destroying the tree takes a lot of time and is unnecessary when there
# is only one input file as the program is about to exit. Note that
# this cleanup is only possible while we still have the value of
# $document.
# this is needed to avoid a last reference on the tree root when checking
# with C code that there are no references left.
$tree = undef;
# refcount should be 1 for pure Perl and 2 with XS.
#print STDERR " $document ".Devel::Peek::SvREFCNT($document).
# " HV: ".Devel::Refcount::refcount($document)."\n"
# .Devel::FindRef::track($document)."\n";
Texinfo::Document::destroy_document($document, $remove_references)
if defined($document);
}
}
foreach my $unclosed_file (keys(%main_unclosed_files)) {
if (!close($main_unclosed_files{$unclosed_file})) {
warn(sprintf(__("%s: error on closing %s: %s")."\n",
$real_command_name, $unclosed_file, $!));
$error_count++;
_exit($error_count, \%opened_files);
}
}
my $texi2dvi = get_conf('TEXI2DVI');
if ($call_texi2dvi) {
if (defined($texi2dvi) and $texi2dvi ne '') {
if (get_conf('DEBUG') or get_conf('VERBOSE')) {
print STDERR "EXEC "
.join('|', (get_conf('TEXI2DVI'), @texi2dvi_args, @ARGV))."\n";
}
exec { get_conf('TEXI2DVI') } (get_conf('TEXI2DVI'), @texi2dvi_args, @ARGV);
} else {
$error_count++;
_exit($error_count, \%opened_files);
}
}
1;