Como estender o conteúdo do arquivo para acrescentar e contar linhas?

Como estender o conteúdo do arquivo para acrescentar e contar linhas?

Aqui estão duas perguntas sobre filecontentsmeio ambiente:

  1. é possível adicionar conteúdo a um arquivo pré-existente ou, no mínimo, manter aberto o arquivo externo para anexar o conteúdo de vários filecontentsambientes ao mesmo arquivo externo?

  2. é possível saber quantas linhas foram gravadas no arquivo externo por um filecontentsambiente?

Responder1

Contar as linhas filecontentsé fácil porque o ambiente processa seu conteúdo linha por linha, lendo, verificando se o ambiente terminou e depois escrevendo. Você só precisa fazer um contador e adicioná-lo toda vez que uma linha for escrita.

Anexar é mais complicado. Em primeiro lugar, o TeX (pelo menos sem Lua) não pode abrir arquivos com permissão "anexar", então isso está descartado. A ideia de deixar o arquivo aberto e retomar a gravação mais tarde parece mais fácil no início, mas você precisaria salvar um monte de variáveis ​​em ambientes e não seria capaz de gravar em file a, then be depois anexar para a.

No código abaixo, \filecontents@preappendmacro (quando a appendopção é usada) lê o arquivo no argumento para uma macro temporária e, em seguida, \filecontents@appendcoloca a macro lida no fluxo de entrada antes do conteúdo real do ambiente, para que funcione como se você digitou tudo de uma vez. A appendopção implica as opções overwritee noheader.

Eu estava tentando escrever o código como um patch, para compactação, mas aparentemente o patch de um comando perde os ^^Jcaracteres e, em seguida, o cabeçalho se reduz a uma única linha, então esta é uma redefinição completa de \filec@ntents, mas as linhas alteradas estão marcadas.

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_eq:NN \FileSize \file_size:n
\ExplSyntaxOff
\makeatletter
\begingroup%
\@tempcnta=1
\loop
  \catcode\@tempcnta=12  %
  \advance\@tempcnta\@ne %
\ifnum\@tempcnta<32      %
\repeat                  %
\catcode`\^^M\active
\catcode`\^^L\active\let^^L\relax
\catcode`\^^I\active
\gdef\filec@ntents#1{%
  \set@curr@file{\filec@ntents@checkdir#1}%
  \edef\q@curr@file{\expandafter\quote@name\expandafter{\@curr@file}}%
  \filecontents@preappend% <- ADDED for appending
  \gaborit@reset@counter% <- ADDED for counting lines
  \gaborit@reset@append% <- ADDED for counting lines
  \openin\@inputcheck\q@curr@file \space %
  \ifeof\@inputcheck%
    \@latex@warning@no@line%
        {Writing file `\@currdir\@curr@file'}%
    \chardef\reserved@c15 %
    \ch@ck7\reserved@c\write%
    \immediate\openout\reserved@c\q@curr@file\relax%
  \else%
    \if@filesw%
      \@latex@warning@no@line%
          {File `\@curr@file' already \filec@ntents@where.\MessageBreak%
             Not generating it from this source}%
      \let\gaborit@step@counter\@empty% <- ADDED for counting lines
      \let\gaborit@count@header\@empty% <- ADDED for counting lines
      \let\write\@gobbletwo%
      \let\closeout\@gobble%
    \else%
      \edef\reserved@a{#1}%
      \edef\reserved@a{\detokenize\expandafter{\reserved@a}}%
      \edef\reserved@b{\detokenize\expandafter{\jobname}}%
      \ifx\reserved@a\reserved@b%
        \@fileswtrue%
      \else%
        \edef\reserved@b{\reserved@b\detokenize{.tex}}%
        \ifx\reserved@a\reserved@b
          \@fileswtrue%
        \fi%
      \fi%
      \chardef\reserved@c15 %
      \ch@ck7\reserved@c\write%
      \if@filesw%  % Foul ... trying to overwrite \jobname!
      \@latex@error{Trying to overwrite `\jobname.tex'}{You can't %
        write to the file you a reading from!\MessageBreak%
        Data is written to screen instead.}%
      \else%
        \@latex@warning@no@line%
           {Writing or overwriting file `\@currdir\@curr@file'}%
        \immediate\openout\reserved@c#1\relax%
      \fi%
    \fi%
  \fi%
  \closein\@inputcheck%
  \if@tempswa%
    \gaborit@count@header% <- ADDED for counting lines
    \immediate\write\reserved@c{%
      \@percentchar\@percentchar\space%
          \expandafter\@gobble\string\LaTeX2e file `\@curr@file'^^J%
      \@percentchar\@percentchar\space  generated by the %
        `\@currenvir' \expandafter\@gobblefour\string\newenvironment^^J%
      \@percentchar\@percentchar\space from source `\jobname' on %
         \number\year/\two@digits\month/\two@digits\day.^^J%
      \@percentchar\@percentchar}%
  \fi%
  \let\do\@makeother\dospecials%
  \count@ 128\relax%
  \loop%
    \catcode\count@ 11\relax%
    \advance\count@ \@ne%
    \ifnum\count@<\@cclvi%
  \repeat%
  \edef\E{\@backslashchar end\string{\@currenvir\string}}%
  \edef\reserved@b{%
    \def\noexpand\reserved@b%
         ####1\E####2\E####3\relax}%
  \reserved@b{%
    \ifx\gaborit@reset@append##1\relax% <- ADDED for counting lines
      \gaborit@reset@append% <- ADDED for counting lines
    \else% <- ADDED for counting lines
    \ifx\relax##3\relax%
      \immediate\write\reserved@c{##1}%
      \gaborit@step@counter% <- ADDED for counting lines
    \else%
      \edef^^M{\noexpand\end{\@currenvir}}%
      \ifx\relax##1\relax%
      \else%
          \@latex@warning{Writing text `##1' before %
             \string\end{\@currenvir}\MessageBreak as last line of \@curr@file}%
        \immediate\write\reserved@c{##1}%
        \gaborit@step@counter% <- ADDED for counting lines
      \fi%
      \ifx\relax##2\relax%
      \else%
         \@latex@warning{%
           Ignoring text `##2' after \string\end{\@currenvir}}%
      \fi%
    \fi%
    \fi% <- ADDED for counting lines
    ^^M}%
  \catcode`\^^L\active%
  \let\L\@undefined%
  \def^^L{\expandafter\ifx\csname L\endcsname\relax\fi ^^J^^J}%
  \catcode`\^^I\active%
  \let\I\@undefined%
  \def^^I{\expandafter\ifx\csname I\endcsname\relax\fi\space}%
  \catcode`\^^M\active%
  \edef^^M##1^^M{%
    \noexpand\reserved@b##1\E\E\relax}%
  \filecontents@append}% <- ADDED for appending
%
% Code for append
\gdef\gaborit@pre@append{%
  \begingroup%
    \catcode`\^^L\active%
    \catcode`\^^I\active%
    \catcode`\^^M\active%
    \let\do\@makeother\dospecials%
    \count@ 128\relax%
    \loop%
      \catcode\count@ 11\relax%
      \advance\count@ \@ne%
      \ifnum\count@<\@cclvi%
    \repeat%
    \let^^M\relax%
    \edef\gaborit@tmpa{\FileSize{\q@curr@file}}%
    \ifnum\expandafter\@car\gaborit@tmpa\@nil=0\relax%
      \endgroup \def\gaborit@append{^^M\gaborit@reset@append}%
    \else%
      \everyeof{\noexpand}%
      \edef\gaborit@tmpa{\@@input\q@curr@file \space}%
      \edef\x{\endgroup%
        \edef\noexpand\gaborit@append{%
          \noexpand\gaborit@trim@EOF\gaborit@tmpa\noexpand\gaborit@EOF}}\x%
    \fi%
  \filec@ntents@overwrite%
  \filec@ntents@noheader}%
\gdef\gaborit@trim@EOF#1^^M\gaborit@EOF{^^M#1%
  ^^M\noexpand\gaborit@reset@append}%
\endgroup%
\def\filec@ntents@append{%
  \let\filecontents@preappend\gaborit@pre@append
  \def\filecontents@append{\gaborit@append}}
\let\filecontents@preappend\@empty
\let\filecontents@append\@empty
%
% For counting lines
\newcounter{FC@total@lines}
\newcounter{FC@lines}
\def\gaborit@reset@counter{\setcounter{FC@total@lines}{0}}
\def\gaborit@reset@append{\setcounter{FC@lines}{0}}
\def\gaborit@step@counter{%
  \stepcounter{FC@total@lines}%
  \stepcounter{FC@lines}}
\def\gaborit@count@header{%
  \addtocounter{FC@total@lines}{4}%
  \addtocounter{FC@lines}{4}} % Number of lines in the header
\makeatother

\begin{filecontents}[overwrite]{testfile.tex}
a
b
c
\end{filecontents}
\typeout{\arabic{FC@total@lines} lines written; \arabic{FC@lines} appended}

\begin{filecontents}[append]{testfile.tex}
d
e
f
g
\end{filecontents}
\typeout{\arabic{FC@total@lines} lines written; \arabic{FC@lines} appended}

\begin{document}
\end{document}

Executando este código, o TeX imprime no terminal:

LaTeX Warning: Writing or overwriting file `./testfile.tex'.

7 lines written; 7 appended
(./testfile.tex)

LaTeX Warning: Writing or overwriting file `./testfile.tex'.

11 lines written; 4 appended

(o FC@total@linescontém o número total de linhas no arquivo e o FC@linescontador contém apenas o número de linhas anexadas) e o arquivo testfile.texcontém:

%% LaTeX2e file `testfile.tex'
%% generated by the `filecontents' environment
%% from source `test' on 2019/12/20.
%%
a
b
c
d
e
f
g


Como você observou, quando você faz:

\begin{filecontents*}{testfile.tex}
\end{filecontents*}
%
\begin{filecontents}[append]{testfile.tex}
a
\end{filecontents}

(ou seja, não escreva nada e anexe uma linha com a) o arquivo obtido é:


a

Isso porque quando o TeX lê um arquivo vazio, ele trata esse arquivo como se fosse um arquivo com uma única linha vazia 1 , portanto você não pode diferenciar facilmente entre esses dois casos e tem que escolher o mal menor: ter um vazio falso linha no início do arquivo ao anexar a um arquivo vazio ou remover uma linha vazia ao gravar em um arquivo que contém uma única linha vazia.

Porém, com os utilitários de arquivo introduzidos no pdfTeX (mas também disponíveis em outros motores hoje em dia) você pode consultar o tamanho (em bytes) de um arquivo, para ter um comportamento diferente para um arquivo vazio. No código usei expl3para \file_size:nevitar ter que lidar com arquivos inexistentes.


1. Você pode ver isso com este arquivo de teste:

\catcode`\@=11
\newwrite\test
\def\testit#1{\immediate\openout\test \jobname.testfile\relax #1%
  {\catcode`\^^M=13 \everyeof{\noexpand}%
   \edef\tmpa{\ifdefined\@@input \@@input \else \input \fi \jobname.testfile }\show\tmpa}%
  \immediate\closeout\test}%
\testit{}% empty file
\testit{\immediate\write\test{}}% single empty line
\csname stop\endcsname\bye

Esse comportamento, eu acho, se deve ao modo como o TeX adiciona um \endlinecharno final de cada linha. Quando ele lê um arquivo vazio, ele ainda lê uma linha (mesmo que vazia) e depois insere o \endlinechar( ^^M) e isso faz parecer que o arquivo tem uma única linha vazia.

informação relacionada