diff --git a/tools/gtk-json-format.c b/tools/gtk-json-format.c new file mode 100644 index 0000000000..9187b08039 --- /dev/null +++ b/tools/gtk-json-format.c @@ -0,0 +1,350 @@ +/* gtk-json-format - Formats JSON data + * + * Copyright © 2013 Emmanuele Bassi + * 2021 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Author: + * Emmanuele Bassi + * Benjamin Otte + */ + +#include "config.h" + +#ifdef G_OS_UNIX +#include +#include +#include +#include +#endif +#include +#ifdef G_OS_WIN32 +#include +#endif + +#include +#include + +#include +#include +#include + +#include "gtk/json/gtkjsonparserprivate.h" +#include "gtk/json/gtkjsonprinterprivate.h" + +#if defined (G_OS_WIN32) && !defined (HAVE_UNISTD_H) +#include + +#define STDOUT_FILENO 1 +#endif + +static char **files = NULL; +static char *output = NULL; +static gboolean ascii = FALSE; +static gboolean prettify = FALSE; +static int indent_spaces = 2; + +static GOptionEntry entries[] = { + { "prettify", 'p', 0, G_OPTION_ARG_NONE, &prettify, N_("Prettify output"), NULL }, + { "indent-spaces", 'i', 0, G_OPTION_ARG_INT, &indent_spaces, N_("Indentation spaces"), N_("SPACES") }, + { "ascii", 0, 0, G_OPTION_ARG_NONE, &ascii, N_("Convert to ASCII instead of UTF-8"), NULL }, + { "output", 'o', 0, G_OPTION_ARG_FILENAME, &output, N_("Output file"), N_("FILE") }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &files, NULL, N_("FILE…") }, + { NULL }, +}; + +typedef struct +{ + gchar buffer[4096]; + gsize size; + int fd; + gboolean close; + int error; +} Writer; + +static void +writer_init (Writer *writer, + int fd, + gboolean close) +{ + writer->size = 0; + writer->fd = fd; + writer->close = close; + writer->error = 0; +} + +static void +writer_flush (Writer *writer, + const char *data, + gsize len) +{ + if (writer->error) + return; + +again: + if (write (writer->fd, data, len) < 0) + { + if (errno == EINTR) + goto again; + writer->error = errno; + } +} + +static int +writer_finish (Writer *writer) +{ + writer_flush (writer, writer->buffer, writer->size); + + if (writer->close) + g_close (writer->fd, NULL); + + return writer->error; +} + +static void +writer_write (GtkJsonPrinter *printer, + const char *s, + gpointer data) +{ + Writer *writer = data; + int len = strlen (s); + + if (sizeof (writer->buffer) - writer->size < len) + { + writer_flush (writer, writer->buffer, writer->size); + writer->size = 0; + } + + if (sizeof (writer->buffer) <= len) + { + writer_flush (writer, s, len); + return; + } + + memcpy (writer->buffer + writer->size, s, len); + writer->size += len; +} + +static void +parse_and_print (GtkJsonParser *parser, + GtkJsonPrinter *printer) +{ + while (TRUE) + { + char *name = gtk_json_parser_get_member_name (parser); + + switch (gtk_json_parser_get_node (parser)) + { + case GTK_JSON_NONE: + if (gtk_json_printer_get_depth (printer) == 0) + return; + gtk_json_printer_end (printer); + gtk_json_parser_end (parser); + break; + + case GTK_JSON_NULL: + gtk_json_printer_add_null (printer, name); + break; + + case GTK_JSON_BOOLEAN: + gtk_json_printer_add_boolean (printer, + name, + gtk_json_parser_get_boolean (parser)); + break; + + case GTK_JSON_NUMBER: + gtk_json_printer_add_number (printer, + name, + gtk_json_parser_get_number (parser)); + break; + + case GTK_JSON_STRING: + { + char *s = gtk_json_parser_get_string (parser); + gtk_json_printer_add_string (printer, name, s); + g_free (s); + } + break; + + case GTK_JSON_OBJECT: + gtk_json_printer_start_object (printer, name); + gtk_json_parser_start_object (parser); + continue; + + case GTK_JSON_ARRAY: + gtk_json_printer_start_array (printer, name); + gtk_json_parser_start_array (parser); + continue; + + default: + g_assert_not_reached (); + return; + } + + g_free (name); + gtk_json_parser_next (parser); + } +} + +static gboolean +format (GtkJsonPrinter *printer, + GFile *file) +{ + GBytes *bytes; + GtkJsonParser *parser; + GError *error = NULL; + + error = NULL; + + bytes = g_file_load_bytes (file, NULL, NULL, &error); + if (bytes == NULL) + { + /* Translators: the first %s is the program name, the second one + * is the URI of the file, the third is the error message. + */ + g_printerr (_("%s: %s: error opening file: %s\n"), + g_get_prgname (), g_file_get_uri (file), error->message); + g_error_free (error); + return FALSE; + } + + parser = gtk_json_parser_new_for_bytes (bytes); + g_bytes_unref (bytes); + parse_and_print (parser, printer); + if (gtk_json_parser_get_error (parser)) + { + char *uri = g_file_get_uri (file); + const GError *parser_error = gtk_json_parser_get_error (parser); + + /* Translators: the first %s is the program name, the second one + * is the URI of the file, the third is the error message. + */ + g_printerr (_("%s: %s: error parsing file: %s\n"), + g_get_prgname (), uri, parser_error->message); + g_free (uri); + gtk_json_parser_free (parser); + return FALSE; + } + + gtk_json_parser_free (parser); + + return TRUE; +} + +int +main (int argc, + char *argv[]) +{ + GOptionContext *context = NULL; + GError *error = NULL; + GtkJsonPrinter *printer; + Writer writer; + gboolean res; + int sv_errno; + int i; + + setlocale (LC_ALL, ""); + + bindtextdomain (GETTEXT_PACKAGE, GTK_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + context = g_option_context_new (NULL); + /* Translators: this message will appear after the usage string */ + /* and before the list of options. */ + g_option_context_set_summary (context, _("Format JSON files.")); + g_option_context_set_description (context, _("json-glib-format formats JSON resources.")); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_parse (context, &argc, &argv, &error); + g_option_context_free (context); + + if (error != NULL) + { + /* Translators: the %s is the program name. This error message + * means the user is calling json-glib-validate without any + * argument. + */ + g_printerr (_("Error parsing commandline options: %s\n"), error->message); + g_printerr ("\n"); + g_printerr (_("Try “%s --help” for more information."), g_get_prgname ()); + g_printerr ("\n"); + g_error_free (error); + return EXIT_FAILURE; + } + + if (files == NULL) + { + /* Translators: the %s is the program name. This error message + * means the user is calling json-glib-validate without any + * argument. + */ + g_printerr (_("%s: missing files"), g_get_prgname ()); + g_printerr ("\n"); + g_printerr (_("Try “%s --help” for more information."), g_get_prgname ()); + g_printerr ("\n"); + return EXIT_FAILURE; + } + + if (output == NULL) + writer_init (&writer, STDOUT_FILENO, FALSE); + else + { + int fd; + + fd = g_open (output, O_CREAT | O_WRONLY, 0666); + if (fd < 0) + { + sv_errno = errno; + + g_printerr (_("%s: %s: error opening file: %s\n"), + g_get_prgname (), output, g_strerror (sv_errno)); + res = FALSE; + return EXIT_FAILURE; + } + writer_init (&writer, fd, TRUE); + } + + printer = gtk_json_printer_new (writer_write, + &writer, + NULL); + gtk_json_printer_set_flags (printer, + (prettify ? GTK_JSON_PRINTER_PRETTY : 0) | + (ascii ? GTK_JSON_PRINTER_ASCII : 0)); + gtk_json_printer_set_indentation (printer, indent_spaces); + + res = TRUE; + i = 0; + + do + { + GFile *file = g_file_new_for_commandline_arg (files[i]); + + res = format (printer, file) && res; + g_object_unref (file); + writer_write (printer, "\n", &writer); + } + while (files[++i] != NULL); + + sv_errno = writer_finish (&writer); + if (sv_errno) + { + g_printerr (_("%s: error writing: %s"), g_get_prgname (), g_strerror (sv_errno)); + res = FALSE; + } + + gtk_json_printer_free (printer); + + return res ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tools/meson.build b/tools/meson.build index 0e90d88945..6f51c9b192 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -33,6 +33,7 @@ gtk_tools = [ 'fake-scope.c'], [libgtk_dep] ], ['gtk4-update-icon-cache', ['updateiconcache.c'] + extra_update_icon_cache_objs, [ libgtk_static_dep ] ], ['gtk4-encode-symbolic-svg', ['encodesymbolic.c'], [ libgtk_static_dep ] ], + ['gtk4-json-format', ['gtk-json-format.c'], [ libgtk_json_dep ], [ libgtk_json ] ], ] if os_unix @@ -43,12 +44,14 @@ foreach tool: gtk_tools tool_name = tool.get(0) tool_srcs = tool.get(1) tool_deps = tool.get(2) + tool_link = tool.get(3, []) exe = executable(tool_name, sources: tool_srcs, include_directories: [confinc], c_args: common_cflags, dependencies: tool_deps, + link_with: tool_link, install: true, )