结论

结论

我有一个很坏的习惯,就是将我的包从一个 LaTeX 文档复制粘贴到另一个文档。有没有什么方法可以检测未使用的包(即\usepackage命令),以便将它们从 LaTeX 文档中删除?

答案1

LaTeX(或其他格式)中的包基本上有三种不同类型:

  1. 扩展 LaTeX 功能的软件包(例如,multicol提供额外的命令和环境来平衡列)
  2. 修改行为​​和布局但不提供任何附加功能的软件包
  3. 可同时执行上述两项功能的混合包。

大多数软件包都属于类型 1) 和 3),即大多数软件包仅更改布局,但通常仍提供一些额外的参数化功能,例如,geometry的主要目的是设置页面布局尺寸,但为了做到这一点,它提供了额外的命令来自定义它(这是类型 3),而不是 2))。但归根结底,这三种类型都存在。

1 类包装

如果您不使用附加功能,则加载或不加载应该没有任何区别。因此,如果您只有这样的包,那么测试将是:“注释掉加载,如果文档编译通过,则您不需要该包”。

2 类包装

这里的情况有所不同:如果不加载它们,文档仍会编译,但输出会有所不同。因此,在这种情况下,测试应该针对对产生的输出的任何更改。

一种简单的测试方法是添加\showoutput序言。这将为每个输出页面生成一个.log可以进行比较的精确符号内容结构。

日志首先需要进行一些清理,以便不会比较其他内容,但本质上它会告诉您:当我没有加载包时文件是否发生了变化。

现在,如果包属于这种类型,为什么它并不总是改变所有文档的行为? 只是因为它可能只改变某些方面,而那些特定的方面不会出现在当前文档中。

3 类包裹

作为混合体,您需要对两者进行测试,如果您不使用扩展功能(丢失时会破坏您的文档),您仍然可能会有布局差异。

结论

由于没有适当的分类来说明软件包属于哪个类别,因此必须假设它们都属于类型 3,因此在编译时如果没有软件包,请注意布局差异。缺失的功能将免费测试(因为它们会导致错误)。

此类测试的完全自动化编程可能有点困难,但也不是那么难。

答案2

我尝试过实现弗兰克·米特尔巴赫的提议每次删除一个包,然后测试每个包是否会改变输出。

这是由一个纯用 LaTeX 编写的脚本自动完成的(需要--shell-escape)。生成的代码非常慢:它必须在文件上运行 TeX 很多次(软件包数量的两倍),并且 TeX 不是解析生成的日志文件的最快工具(实际上,该步骤可以更快)。

要使用它,请将以下代码存储在一个文件中,例如packagecheck.tex,然后调用您通常输入的命令pdflatex --shell-escape packagecheck <engine> <filename><engine> <filename>

%https://tex.stackexchange.com/questions/78073/detecting-unused-packages-in-latex-document?lq=1
\ifnum\pdfshellescape=1\else
  \PackageError{packagecheck}
    {Misused\MessageBreak
      pdflatex --shell-escape packagecheck\MessageBreak
      <engine> <file-name>}
    {}
  \csname @@end\expandafter\endcsname
  \csname bye\expandafter\endcsname
\fi
\RequirePackage{expl3, l3regex}
\ExplSyntaxOn
% \begin{macro}{\log_map_inline:nn, \log_map_inline:Vn}
%   \begin{syntax}
%     \cs{log_map_inline:nn} \Arg{file name} \Arg{code}
%   \end{syntax}
%   Goes through the file \meta{file name}\texttt{.log} and performs the
%   \meta{code} for each logical line of the log file (assuming that any
%   line with $79$ characters in the log file is to be concatenated with
%   the following one).  The \meta{code} receives the line as |#1|, as a
%   string.  For instance the following shows each line in |texput.log|.
%   \begin{verbatim}
%     \log_map_inline:nn { texput } { \tl_show:n {#1} }
%   \end{verbatim}
%    \begin{macrocode}
\int_const:Nn \c_log_line_count_int { 79 }
\int_new:N \l_log_line_int
\int_new:N \l_log_max_line_int
\ior_new:N \g_log_ior
\str_new:N \l_log_line_str
\str_new:N \l_log_tmp_str
\cs_new_protected:Npn \__log_tmp:n #1 { }
\cs_new_protected:Npn \log_map_inline:nn #1#2 % not nestable
  {
%<*trace>
    \int_zero:N \l_log_line_int
    \ior_open:Nn \g_log_ior { #1 . log }
    \ior_str_map_inline:Nn \g_log_ior { \int_incr:N \l_log_line_int }
    \ior_close:N \g_log_ior
    \msg_term:n
      {
        Opening~file~#1.log~( \int_use:N \l_log_line_int \ lines).~
        One~dot~per~100~lines.
      }
    \int_zero:N \l_log_line_int
%</trace>
    \cs_set_protected:Npn \__log_tmp:n ##1 {#2}
    \ior_open:Nn \g_log_ior { #1 . log }
    \ior_str_map_inline:Nn \g_log_ior
      {
%<*trace>
        \int_incr:N \l_log_line_int
        \if_int_compare:w \l_log_line_int > 100 \scan_stop:
          \tex_message:D { . }
          \int_zero:N \l_log_line_int
        \fi:
%</trace>
        \str_set:Nn \l_log_tmp_str {##1}
        \tl_set_eq:NN \l_log_line_str \l_log_tmp_str
        \int_until_do:nNnn
          { \str_count:N \l_log_tmp_str } < \c_log_line_count_int
          {
            \ior_get_str:NN \g_log_ior \l_log_tmp_str
            \str_put_right:Nx \l_log_line_str \l_log_tmp_str
          }
        \exp_args:No \__log_tmp:n \l_log_line_str
      }
    \ior_close:N \g_log_ior
  }
\cs_generate_variant:Nn \log_map_inline:nn { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\log_get_packages:nN, \log_get_packages:VN}
%   \begin{macrocode}
\seq_new:N \l_log_packages_seq
\seq_new:N \l_log_tmp_seq
\regex_const:Nn \c_package_regex { \w* \. sty }
\cs_new_protected:Npn \log_get_packages:nN #1#2
  {
    \seq_clear:N \l_log_packages_seq
    \log_map_inline:nn {#1}
      {
        \regex_extract_all:NnN \c_package_regex {##1} \l_log_tmp_seq
        \seq_concat:NNN \l_log_packages_seq
          \l_log_packages_seq \l_log_tmp_seq
      }
    \seq_remove_duplicates:N \l_log_packages_seq
    \tl_set:Nx #2
      { \seq_map_function:NN \l_log_packages_seq \prg_do_nothing: }
  }
\cs_generate_variant:Nn \log_get_packages:nN { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\log_get_pages:nN}
%   \begin{macrocode}
\tl_new:N \l_log_pages_tl
\str_new:N \l_log_page_str
\bool_new:N \l_log_in_page_bool
\cs_new_protected:Npn \log_get_pages:nN #1#2
  {
    \tl_clear:N \l_log_pages_tl
    \tl_clear:N \l_log_page_str
    \bool_set_false:N \l_log_in_page_bool
    \log_map_inline:nn {#1} % could be optimized away
      {
        \bool_if:NTF \l_log_in_page_bool
          {
            \tl_if_empty:nTF {##1}
              {
                \bool_set_false:N \l_log_in_page_bool
                \tl_put_right:Nx \l_log_pages_tl { { \l_log_page_str } }
              }
              { \str_put_right:Nx \l_log_page_str { ##1 \iow_newline: } }
          }
          {
            \str_if_eq_x:nnT
              { Completed } { \__log_get_pages_test:ww ##1 ~ \q_stop }
              {
                \str_if_eq_x:nnT
                  { Completed~box~being~shipped~out~ }
                  { \str_substr:nnn {##1} { 1 } { 32 } }
                  {
                    \bool_set_true:N \l_log_in_page_bool
                    \str_put_right:Nx \l_log_page_str { ##1 \iow_newline: }
                  }
              }
          }
      }
    \tl_set_eq:NN #2 \l_log_pages_tl
  }
\cs_new:Npn \__log_get_pages_test:ww #1 ~ #2 \q_stop {#1}
\cs_generate_variant:Nn \log_get_pages:nN { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[aux]{\__run:n}
%   Compile the file once, halting if there is an error, and in
%   batchmode to suppress the output.  First output a line to separate
%   the output of the internal \TeX{} run from the main run.
%    \begin{macrocode}
\cs_new_protected:Npn \__run:n #1
  {
    \iow_term:x { }
    \tex_immediate:D \tex_write:D 18
      {
        \l_run_engine_str \c_space_tl
        -halt-on-error \c_space_tl
        -interaction=batchmode \c_space_tl
        '\tl_to_str:n { #1 \input }' \c_space_tl
        \l_run_file_str
      }
  }
%    \end{macrocode}
% \end{macro}
%
\str_new:N \l_run_engine_str
\str_new:N \l_run_file_str
\tl_new:N \l_run_packages_tl
\tl_new:N \l_run_normal_pages_tl
\tl_new:N \l_run_pages_tl
\seq_new:N \l_run_used_packages_seq
\seq_new:N \l_run_unused_packages_seq
\int_new:N \l_run_package_int
\int_new:N \l_run_package_max_int
\cs_new_protected:Npn \__run_get_pages:nN #1#2
  {
    \__run:n {#1}
    \__run:n { #1 \loggingoutput \batchmode }
    \log_get_pages:VN \l_run_file_str #2
  }
\cs_new_protected:Npn \run:ww #1~#2~
  {
    \str_set:Nx \l_run_engine_str {#1}
    \str_set:Nx \l_run_file_str {#2}
    \regex_replace_once:nnN { \. tex \Z } { } \l_run_file_str
    \__run:n { }
    \log_get_packages:VN \l_run_file_str \l_run_packages_tl
    \__run_get_pages:nN { } \l_run_normal_pages_tl
%<*trace>
    \int_set:Nn \l_run_package_max_int { \tl_count:N \l_run_packages_tl }
    \int_zero:N \l_run_package_int
%</trace>
    \tl_map_inline:Nn \l_run_packages_tl
      {
%<*trace>
        \int_incr:N \l_run_package_int
        \msg_term:n
          {
            Package~\int_use:N \l_run_package_int                 of~\int_use:N \l_run_package_max_int
          }
%</trace>
        \__run_get_pages:nN
          { \expandafter \let \csname ver @ ##1 \endcsname \empty }
          \l_run_pages_tl
        \tl_if_eq:NNTF \l_run_pages_tl \l_run_normal_pages_tl
          { \seq_put_right:Nn \l_run_unused_packages_seq {##1} }
          { \seq_put_right:Nn \l_run_used_packages_seq {##1} }
      }
    \seq_if_empty:NF \l_run_used_packages_seq
      {
        \msg_info:nnxx { run } { used-packages }
          { \seq_count:N \l_run_used_packages_seq }
          {
            \seq_use:Nnnn \l_run_used_packages_seq
              { ~ and ~ } { , ~ } { , ~ and ~ }
          }
      }
    \seq_if_empty:NF \l_run_unused_packages_seq
      {
        \msg_warning:nnxx { run } { unused-packages }
          { \seq_count:N \l_run_unused_packages_seq }
          {
            \seq_use:Nnnn \l_run_unused_packages_seq
              { ~ and ~ } { , ~ } { , ~ and ~ }
          }
      }
    \msg_term:n
      {
        \fp_to_tl:n { round(\pdfelapsedtime/65536,1) }
        \ seconds
      }
    \tex_end:D
  }
\msg_new:nnn { run } { used-packages }
  {
    \int_compare:nTF { #1 = 1 }
      { The~package~#2~is~useful~in~this~document }
      { The~#1~packages~#2~are~useful~in~this~document. }
  }
\msg_new:nnn { run } { unused-packages }
  {
    Removing~
    \int_compare:nTF { #1 = 1 } { the~package~ } { any~of~the~packages~ }
    #2~changes~nothing~in~the~resulting~output.
  }
\use:n
  {
    \ExplSyntaxOff
    \tex_endinput:D
    \exp_after:wN \run:ww
  }%

正如我所说,这非常慢,例如,pdflatex --shell-escape packagecheck pdflatex test.tex在文件上运行test.tex

\documentclass{article}
\usepackage{lipsum}
\usepackage{amsgen}
\begin{document}
\lipsum[1-10]
\end{document}

在我的笔记本电脑上花了 5 分钟才告诉我amsgen该文档中未使用,但是lipsum确实使用了。

工作原理:主要工具是调用\immediate\write18{<engine> -halt-on-error -interaction=batchmode <TeX code> <filename>}其中<engine>,例如,pdflatex确保-halt-on-errorTeX 不会尝试继续执行第一个错误(删除某些包时会出现错误),-interaction=batchmode最小化“内部”运行的输出,<TeX code>是一些随不同运行而变化的代码,并且<filename>,嗯,是需要编译的文件的名称。

  • 第一次运行,没有额外的代码。这会生成一个日志文件,我们会在其中查找所有匹配的内容\w*.sty(单词字符后跟sty)。也许我应该搜索[\w\-]*.sty,或者其他内容。

  • 然后运行两次以产生正确的输出,第一次不运行,第二次运行\loggingoutput\batchmode,这确保(1)输出页面在日志文件中详细列出(2)没有任何其他内容写入终端。

  • 然后,对于每个包,运行两次,第一次不使用,第二次使用\loggingoutput\batchmode,通过在每次运行之前定义来禁用该特定包的加载\ver@<package name>.sty,以使 LaTeX 认为该包已加载。将日志文件中找到的页面与上一步中找到的正常页面进行比较,以确定该包是否有用。

  • 最后,显示所有无用包的列表以及已用时间。

答案3

上面由 Vasily Sidorov (@Bazzilic) 提出的解决方案很不错,但也存在一些限制,它只能在 Windows 上运行,仅限于 pdflatex,编辑原始项目的 .tex 文件,......

在与 Vasily 断绝联系后,我分叉了代码,现在它可以在 Linux 和 Windows 上运行。您可以选择任何 latex 引擎:latex、pdflatex、xelatex 和 lualatex,以及用于文献处理的 BibTeX 或 BibLaTeX。它在您的 src 代码副本上运行,并具有 --debug 标志,可留下所有中间文件。

我还添加了一个 --visual 选项,将 DVI/PDF 文件转换为高分辨率 JPG,并比较它们,尽管 PDF 级别存在差异。

它已经经过了广泛的测试,并且运行良好。当然,我们希望听到其他测试人员的意见和改进建议。

代码可从以下位置获取:

https://github.com/TarasKuzyo/LaTeXpkges

答案4

我自己也在寻找这个问题的解决方案,然后偶然发现了这个 tex.SE 主题。意识到这个问题没有真正的解决方案,我擅自编写了一个简单的实用程序脚本来解决这个问题。

该实用程序是用 Python 编写的,它是开源的,可以在这里访问:https://github.com/bazzilic/LaTeXpkges。有关其工作原理的更多信息,请参阅 github 上的自述文件。

它是为 Windows 平台编写的,可能无法在其他平台上使用。它目前处于非常基本的形式,我只在我的论文中测试过它(它运行良好)。如果您在系统上安装了 Python,您可以下载脚本,或者转到 Release 选项卡并下载带有 Windows 可执行文件的 zip(使用 py2exe 创建)。

重要的:不是针对原始文件运行它!制作一份副本并针对副本运行它!

相关内容