+static const struct output_driver_factory *
+find_factory (const char *format)
+{
+ const struct output_driver_factory **fp;
+
+ for (fp = factories; *fp != NULL; fp++)
+ {
+ const struct output_driver_factory *f = *fp;
+
+ if (!strcmp (f->extension, format))
+ return f;
+ }
+ return &txt_driver_factory;
+}
+
+static enum settings_output_devices
+default_device_type (const char *file_name)
+{
+ return (!strcmp (file_name, "-")
+ ? SETTINGS_DEVICE_TERMINAL
+ : SETTINGS_DEVICE_LISTING);
+}
+
+struct output_driver *
+output_driver_create (struct string_map *options)
+{
+ char *format = string_map_find_and_delete (options, "format");
+ char *file_name = string_map_find_and_delete (options, "output-file");
+ if (!format)
+ {
+ if (file_name)
+ {
+ const char *extension = strrchr (file_name, '.');
+ format = xstrdup (extension != NULL ? extension + 1 : "");
+ }
+ else
+ format = xstrdup ("txt");
+ }
+ const struct output_driver_factory *f = find_factory (format);
+
+ struct driver_options o = {
+ .driver_name = f->extension,
+ .map = STRING_MAP_INITIALIZER (o.map),
+ .garbage = STRING_ARRAY_INITIALIZER,
+ };
+ string_map_swap (&o.map, options);
+
+ if (file_name == NULL)
+ file_name = xstrdup (f->default_file_name);
+
+ enum settings_output_devices default_type = default_device_type (file_name);
+ const char *default_type_string = (default_type == SETTINGS_DEVICE_TERMINAL
+ ? "terminal" : "listing");
+ enum settings_output_devices device_type = parse_enum (
+ driver_option_get (&o, "device", default_type_string),
+ "terminal", SETTINGS_DEVICE_TERMINAL,
+ "listing", SETTINGS_DEVICE_LISTING,
+ NULL_SENTINEL);
+
+ struct file_handle *fh = fh_create_file (NULL, file_name, NULL,
+ fh_default_properties ());
+ struct output_driver *driver = f->create (fh, device_type, &o);
+ if (driver)
+ {
+ const struct string_map_node *node;
+ const char *key;
+
+ STRING_MAP_FOR_EACH_KEY (key, node, &o.map)
+ msg (MW, _("%s: unknown option `%s'"), file_name, key);
+ }
+
+ string_map_destroy (&o.map);
+ string_array_destroy (&o.garbage);
+
+ free (file_name);
+ free (format);
+
+ return driver;
+}
+
+void
+output_driver_parse_option (const char *option, struct string_map *options)
+{
+ const char *equals = strchr (option, '=');
+ if (equals == NULL)
+ {
+ error (0, 0, _("%s: output option missing `='"), option);
+ return;
+ }
+
+ char *key = xmemdup0 (option, equals - option);
+ if (string_map_contains (options, key))
+ {
+ error (0, 0, _("%s: output option specified more than once"), key);
+ free (key);
+ return;
+ }
+
+ char *value = xmemdup0 (equals + 1, strlen (equals + 1));
+ string_map_insert_nocopy (options, key, value);
+}
+\f
+char *
+output_driver_substitute_heading_vars (const char *src, int page_number)
+{
+ struct output_engine *e = engine_stack_top ();
+ struct string dst = DS_EMPTY_INITIALIZER;
+ ds_extend (&dst, strlen (src));
+ for (const char *p = src; *p;)
+ {
+ if (!strncmp (p, "&[", 6))
+ {
+ if (page_number != INT_MIN)
+ {
+ const char *start = p + 6;
+ const char *end = strchr (start, ']');
+ if (end)
+ {
+ const char *value = string_map_find__ (&e->heading_vars,
+ start, end - start);
+ if (value)
+ ds_put_cstr (&dst, value);
+ else if (ss_equals (ss_buffer (start, end - start),
+ ss_cstr ("Page")))
+ ds_put_format (&dst, "%d", page_number);
+ p = end + 1;
+ continue;
+ }
+ }
+ ds_put_cstr (&dst, "&");
+ p += 5;
+ }
+ else
+ ds_put_byte (&dst, *p++);
+ }
+ return ds_steal_cstr (&dst);
+}