Como dice el título, fmtroff es una versión más del viejo
fmt(1) que encontrará en la mayoría de sistemas tipo
Unix[*]. Esta versión, además de mejorar algunas
características presentes en otras versiones de fmt, también
trae alguna innovación para hacer más fácil y fiable trabajar con
archivos roff (para editar mis novelas usé groff, la versión
de GNU).
Descargar (fmtroff.c)
Probado en OpenBSD y Linux. Espero que lo encuentre
útil.
Cronología de cambios
- 1 de septiembre de 2025. Se me había escapado
detectar el espacio precedente en el tercer caso de detección de
abreviaturas (is_initial()).
-
20 de agosto de 2025. Cambié la siguiente
notación:
#define PERIOD 0x2e
#define QUESTION 0x3f
[...]
Por:
#define PERIOD L'.'
#define QUESTION L'?'
[...]
Ingo Schwarze, del equipo de OpenBSD me hizo notar que la notación
0x2e al definir una constante es considerda del tipo
int (ver C11 6.4.4.1, Integer
constant). Yo estaba convirtiendo int a
wchar_t, lo que no tiene sentido. Para definirlas
como constantes de characteres la notación que corresponde es
L'.' (ver C11 6.4.4.4, Character constant).
-
19 de agosto de 2025. Volví a implementar la
simplificación del 17 de agosto. No se trataba de un
problema con los compiladores sino de un fallo mío, se me había
escapado acabar con valor nulo las matrices constantes de
caracteres. Por definición, si la matriz no acaba en
caracter nulo ('\0') no es una cadena
(string). Conceptos básicos que a nuestro cerebro
humano se le escapan pero no al compilador. Antes:
const wchar_t END_OF_SENTENCE[] = { PERIOD, QUESTION, EXCLAM,
ELLIPSIS };
Ahora:
const wchar_t END_OF_SENTENCE[] = { PERIOD, QUESTION, EXCLAM,
ELLIPSIS, '\0' };
Esto añadía basura en la cadena confundiendo a funciones como
wcsspn() que retornaban valores
equivocados. Agradezco a Otto Moerbeek del equipo de OpenBSD
por hacérmelo notar.
- 18 de agosto de 2025. Lamentablemente tuve que
revertir el cambio anterior. El cambio introdujo un bug en
OpenBSD no reproducible en Linux. En la entrada del 5 de
Junio mencioné que compilando en Linux el comportamiento del ejecutable
era diferente con respecto a hacerlo en OpenBSD. Hoy instalé
GCC (GNU cc) en OpenBSD y corroboré que GCC interpreta el código de
distinta manera a como lo hace Clang.
- 17 de agosto de 2025. Separé la detección de
abreviaturas a una función aparte para ordenar un poco el código.
- 10 de agosto de 2025. Revertí un cambio que
provocaba un error de memoria en Linux.
- 8 de agosto de 2025. Más mejoras en
reconocimiento de abreviaturas.
- 2 de agosto de 2025. Reconocer abreviaturas
incluso si están entre comillas o paréntesis.
- 2 de agosto de 2025. Pequeña modificación en el
reconocimiento de abreviaturas. No sólo una mayúscula, una
letra minúscula precedida de espacio (o signo de acotación) y seguida
de punto también es seguramente una abreviatura.
- 5 de junio de 2025. Últimamente, usando Linux,
encontré dos bugs que no se reproducían en OpenBSD.
- En la función cquote_count() (ex
left_quote()), no limitar el loop (regresivo)
producía un segfault cuando un símbolo de acotación seguido
por espacios se encontraba al principio de la línea.
- Me di cuenta de que en Linux, para que la función
wcsspn() retorne los valores esperados es necesario declarar
como constantes las matrices utilizadas como argumentos (en este caso
end_of_sentence[], oquote[] y
cquote[]). Para lograr el mismo efecto en OpenBSD
hay que declararlas como estáticas. Supongo que esto se debe
a que los compiladores gcc y clang interpretan el
código de diferente manera.
- 5 de junio de 2025. Cambié el nombre de la
función left_quote() por uno más apropiado:
cquote_count(). También simplifiqué el nombre de
las matrices cl_quote[] and op_quote[], ahora
cquote[] y oquote[] respectivamente.
- 28 de enero de 2025. Quité un par de
abreviaturas que había introducido por equivocación.
- 6 de enero de 2025. En modo "troff", además de
las líneas que comienzan con punto o apóstrofe, ignorar también las que
empiezan con barra inclinada invertida.
- 26 de octubre de 2024. Reconocer barra
invertida ('\') como caracter de inicio de
oración. Esto permite usar etiquetas tanto de roff como de
LaTeX al inicio de la oración (ej. \fI o
\emph{).
- 2 de octubre de 2024. Decidí revertir los
cambios del 10 y el 22 de agosto desde que complicaban el código sólo
para cubrir casos aislados.
- 26 de septiembre de 2024. Ayer noté que al
ejecutar fmtroff en texto chino muchos caracteres se
borraban acortando la cadena. Esto sucedía en Linux, como yo
normalmente uso OpenBSD (y tampoco suelo escribir en chino ;-)) no lo
había notado. Después de investigar, me di cuenta de que la
causa estaba en collapse_whitespace(), donde usaba
isblank() en lugar de
iswblank(). Pensaba que en este caso no era
necesario usar la versión de caracteres anchos de la función, pero
estaba equivocado.
- 22 de agosto de 2024. Antes de salvar la
variable explicada en el item anterior, comprobar que la palabra
contiene sólo letras para asegurarse de que es un nombre de
persona.
- 10 de agosto de 2024. Salvar en una variable si
la presente palabra empieza con mayúsculas (lo que sugiere que es un
nombre) para reconocer si la que sigue es una inicial.
- 9 de mayo de 2024. Simplifiqué un poco el
código.
- 7 de mayo de 2024. Agregué la opción
‘-m’ que, activada, indica a fmtroff que evite
dar formato a los encabezados de correo electrónico y al texto acotado
(agregué esta opción sólo por ser coherente con el uso original de
fmt(1)).
- 7 de mayo de 2024. Cambié la opción
‘-t’ por ‘-b’ y la opción ‘-l’ por
‘-o’, para ganar compatibilidad con la versión de
fmt(1) de OpenBSD.
- 6 de mayo de 2024. Ahora fmtroff
evita formatear el código anidado de tablas, gráficos y ecuaciones
(tbl(1), pic(1),
eqn(1)). Gracias Victor <vico arroba tuta
punto io> por indicármelo.
- 7 de noviembre de 2023 (bug). Corregido un
error creado en las últimas modificaciones.
- 8 de octubre de 2023. Una vez más reescribí
todo el código ahora usando funciones de las bibliotecas de C y de
caracteres anchos.
- 1 de octubre de 2023. Reescribí todo el código
usando punteros en lugar de matrices de longitud variable.
- 8 de septiembre de 2023. A excepción de ‘nueva
línea’ y ‘tabulador’, fmtroff limpia los llamados caracteres de
control. Hoy leyendo una interesante discusión en las listas
de correo de groff me enteré de que con roff se puede utilizar el
caracter leader o SOH (ASCII 0x01) en tablas o
índices, de ahí que modifiqué el código para que, en modo ‘troff’
(opción ‘-t’), fmtroff preserve este caracter.
- 24 de agosto de 2023 (bug). Hoy descubrí que es
necesario reservar memoria antes de entrar al loop que lee el
fichero (o cadena de entrada). Sin esto llamar fmtroff desde
un editor de texto (nvi o vim) en un fichero vacío produce un
segfault.
- 28 de junio de 2023. Mejoré el reconocimiento
de iniciales para que ignore paréntesis o comillas y le agregué soporte
de mayúsculas no ASCII.
- 25 de abril de 2023 (bug). Pasaron años sin
usar iso-latin, por esto no fue hasta ayer que, mientras tecleaba algún
texto en la consola de OpenBSD, me di cuenta de que fmtroff se colgaba
al encontrar caracteres iso-latin. Tomó sólo modificar un
condicional para corregir el error, lo que significa que ahora la
limitación de UTF-8 sólo se aplica a caracteres multibyte.
- 6 de abril de 2023 (bug). En modo troff o
siempre que la opción ‘-n’ no se utilice, tratar las líneas
que comienzan con ‘'’ (comilla simple) igual que las que
comienzan con punto, ya que aquellas también son utilizadas en macros
de troff.
- 5 de abril de 2023. No agregar doble espacio
luego de puntos suspensivos cuando éstos se hallan al principio de la
línea.
- 28 de marzo de 2023. Nueva opción
‘-l’, permite que las oraciones comiencen con minúscula
(útil con páginas de manual donde comenzar la oración con un nombre de
comando es recurrente).
- 25 de marzo de 2023 (bug). Ignorar palabra
cuando su número de caracteres supera el límite de columnas (ej
URLs).