tdv/src/tdv-transform.c

227 lines
6.8 KiB
C

/*
* A tool to transform dictionaries dictionaries by an external filter
*
* The external filter needs to process NUL-separated textual entries.
*
* Example: tdv-transform input.ifo output -- perl -p0e s/bullshit/soykaf/g
*
* Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <locale.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <glib-unix.h>
#include <gio/gio.h>
#include "stardict.h"
#include "stardict-private.h"
#include "generator.h"
#include "utils.h"
enum { PIPE_READ, PIPE_WRITE };
// --- Main --------------------------------------------------------------------
static inline void
print_progress (gulong *last_percent, StardictIterator *iterator, gsize total)
{
gulong percent =
(gulong) stardict_iterator_get_offset (iterator) * 100 / total;
if (percent != *last_percent)
{
printf ("\r Writing entries... %3lu%%", percent);
*last_percent = percent;
}
}
static gboolean
write_to_filter (StardictDict *dict, gint fd, GError **error)
{
StardictInfo *info = stardict_dict_get_info (dict);
gsize n_words = stardict_info_get_word_count (info);
StardictIterator *iterator = stardict_iterator_new (dict, 0);
gulong last_percent = -1;
while (stardict_iterator_is_valid (iterator))
{
print_progress (&last_percent, iterator, n_words);
StardictEntry *entry = stardict_iterator_get_entry (iterator);
for (const GList *fields = stardict_entry_get_fields (entry);
fields; fields = fields->next)
{
StardictEntryField *field = fields->data;
if (!g_ascii_islower (field->type))
continue;
if (write (fd, field->data, field->data_size)
!= (ssize_t) field->data_size)
{
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
}
g_object_unref (entry);
stardict_iterator_next (iterator);
}
printf ("\n");
return TRUE;
}
static gboolean
update_from_filter (StardictDict *dict, Generator *generator,
GMappedFile *filtered_file, GError **error)
{
gchar *filtered = g_mapped_file_get_contents (filtered_file);
gchar *filtered_end = filtered + g_mapped_file_get_length (filtered_file);
StardictInfo *info = stardict_dict_get_info (dict);
gsize n_words = stardict_info_get_word_count (info);
StardictIterator *iterator = stardict_iterator_new (dict, 0);
gulong last_percent = -1;
while (stardict_iterator_is_valid (iterator))
{
print_progress (&last_percent, iterator, n_words);
StardictEntry *entry = stardict_iterator_get_entry (iterator);
generator_begin_entry (generator);
for (GList *fields = entry->fields; fields; fields = fields->next)
{
StardictEntryField *field = fields->data;
if (!g_ascii_islower (field->type))
continue;
gchar *end = memchr (filtered, 0, filtered_end - filtered);
if (!end)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
"filter seems to have ended too early");
return FALSE;
}
g_free (field->data);
field->data = g_strdup (filtered);
field->data_size = end - filtered + 1;
filtered = end + 1;
}
if (!generator_write_fields (generator, entry->fields, error)
|| !generator_finish_entry (generator,
stardict_iterator_get_word (iterator), error))
return FALSE;
g_object_unref (entry);
stardict_iterator_next (iterator);
}
printf ("\n");
return TRUE;
}
int
main (int argc, char *argv[])
{
// The GLib help includes an ellipsis character, for some reason
(void) setlocale (LC_ALL, "");
GError *error = NULL;
GOptionContext *ctx = g_option_context_new
("input.ifo output-basename -- FILTER [ARG...]");
g_option_context_set_summary
(ctx, "Transform dictionaries using a filter program.");
if (!g_option_context_parse (ctx, &argc, &argv, &error))
fatal ("Error: option parsing failed: %s\n", error->message);
if (argc < 3)
fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
// GLib is bullshit, getopt_long() always correctly removes this
gint program_argv_start = 3;
if (!strcmp (argv[program_argv_start], "--"))
program_argv_start++;
g_option_context_free (ctx);
printf ("Loading the original dictionary...\n");
StardictDict *dict = stardict_dict_new (argv[1], &error);
if (!dict)
fatal ("Error: opening the dictionary failed: %s\n", error->message);
printf ("Filtering entries...\n");
gint child_in[2];
if (!g_unix_open_pipe (child_in, 0, &error))
fatal ("g_unix_open_pipe: %s\n", error->message);
FILE *child_out = tmpfile ();
if (!child_out)
fatal ("tmpfile: %s\n", g_strerror (errno));
GPid pid = -1;
if (!g_spawn_async_with_fds (NULL /* working_directory */,
argv + program_argv_start /* forward a part of ours */, NULL /* envp */,
G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
NULL /* child_setup */, NULL /* user_data */,
&pid, child_in[PIPE_READ], fileno (child_out), STDERR_FILENO, &error))
fatal ("g_spawn: %s\n", error->message);
if (!write_to_filter (dict, child_in[PIPE_WRITE], &error))
fatal ("write_to_filter: %s\n", error->message);
if (!g_close (child_in[PIPE_READ], &error)
|| !g_close (child_in[PIPE_WRITE], &error))
fatal ("g_close: %s\n", error->message);
printf ("Waiting for the filter to finish...\n");
int wstatus = errno = 0;
if (waitpid (pid, &wstatus, 0) < 1
|| !WIFEXITED (wstatus) || WEXITSTATUS (wstatus) > 0)
fatal ("Filter failed (%s, status %d)\n", g_strerror (errno), wstatus);
GMappedFile *filtered = g_mapped_file_new_from_fd (fileno (child_out),
FALSE /* writable */, &error);
if (!filtered)
fatal ("g_mapped_file_new_from_fd: %s\n", error->message);
printf ("Writing the new dictionary...\n");
Generator *generator = generator_new (argv[2], &error);
if (!generator)
fatal ("Error: failed to create the output dictionary: %s\n",
error->message);
StardictInfo *info = generator->info;
stardict_info_copy (info, stardict_dict_get_info (dict));
// This gets incremented each time an entry is finished
info->word_count = 0;
if (!update_from_filter (dict, generator, filtered, &error)
|| !generator_finish (generator, &error))
fatal ("Error: failed to write the dictionary: %s\n", error->message);
g_mapped_file_unref (filtered);
fclose (child_out);
generator_free (generator);
g_object_unref (dict);
return 0;
}