/* sane-desc.c -- generate list of supported SANE devices Copyright (C) 2002, 2003 Henning Meier-Geinitz This file is part of the SANE package. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define SANE_DESC_VERSION "1.0" #define MAN_PAGE_LINK "http://www.mostang.com/sane/man/%s.5.html" #define COLOR_ALPHA "\"#B00000\"" #define COLOR_BETA "\"#B0B000\"" #define COLOR_STABLE "\"#008000\"" #define COLOR_UNTESTED "\"#0000B0\"" #define COLOR_UNSUPPORTED "\"#F00000\"" #define COLOR_NEW "\"#F00000\"" #include <../include/sane/config.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../include/sane/sane.h" #include "../include/sane/sanei.h" #include "../include/sane/sanei_config.h" #ifndef PATH_MAX # define PATH_MAX 1024 #endif #define DBG_ERR current_debug_level = 0; debug_call #define DBG_WARN current_debug_level = 1; debug_call #define DBG_INFO current_debug_level = 2; debug_call #define DBG_DBG current_debug_level = 3; debug_call typedef enum output_mode { output_mode_ascii = 0, output_mode_html_backends, output_mode_html_backends_split, output_mode_html_mfgs } output_mode; typedef enum parameter_type { param_none = 0, param_string } parameter_type; typedef enum status_entry { status_unknown, status_alpha, status_beta, status_stable, status_untested, status_unsupported } status_entry; typedef enum device_type { type_unknown, type_scanner, type_stillcam, type_vidcam, type_meta, type_api } device_type; typedef enum level { level_backend, level_mfg, level_model, level_desc } level; typedef struct url_entry { struct url_entry *next; char *name; } url_entry; typedef struct model_entry { struct model_entry *next; char *name; char *interface; struct url_entry *url; char *comment; enum status_entry status; } model_entry; typedef struct desc_entry { struct desc_entry *next; char *desc; struct url_entry *url; char *comment; } desc_entry; typedef struct mfg_entry { struct mfg_entry *next; char *name; struct url_entry *url; char *comment; struct model_entry *model; } mfg_entry; typedef struct type_entry { struct type_entry *next; enum device_type type; struct desc_entry *desc; struct mfg_entry *mfg; } type_entry; typedef struct backend_entry { struct backend_entry *next; char *name; char *version; enum status_entry status; char *manpage; struct url_entry *url; char *comment; struct type_entry *type; SANE_Bool new; } backend_entry; typedef struct model_record_entry { struct model_record_entry *next; char *name; char *interface; struct url_entry *url; char *comment; enum status_entry status; struct backend_entry *be; } model_record_entry; typedef struct mfg_record_entry { struct mfg_record_entry *next; char *name; char *comment; struct url_entry *url; struct model_record_entry *model_record; } mfg_record_entry; static char *program_name; static int debug = 0; static int current_debug_level = 0; static char *search_dir = 0; static backend_entry *first_backend = 0; static enum output_mode mode = output_mode_ascii; static char *title = 0; static char *intro = 0; static void debug_call (const char *fmt, ...) { va_list ap; char *level_txt; va_start (ap, fmt); if (debug >= current_debug_level) { /* print to stderr */ switch (current_debug_level) { case 0: level_txt = "ERROR:"; break; case 1: level_txt = "Warning:"; break; case 2: level_txt = "Info:"; break; default: level_txt = ""; break; } fprintf (stderr, "[%s] %8s ", program_name, level_txt); vfprintf (stderr, fmt, ap); } va_end (ap); } static void print_usage (char *program_name) { printf ("Usage: %s [-s dir] [-m mode] [-d level] [-h] [-V]\n", program_name); printf (" -s|--search-dir dir Specify the directory that contains " ".desc files\n"); printf (" -m|--mode mode Output mode (ascii, html-backends-split,\n" " html-backends, html-mfgs)\n"); printf (" -t|--title \"title\" The title used for HTML pages\n"); printf (" -i|--intro \"intro\" A short description of the " "contents of the page\n"); printf (" -d|--debug-level level Specify debug level (0-3)\n"); printf (" -h|--help Print help message\n"); printf (" -V|--version Print version information\n"); printf ("Report bugs to \n"); } static void print_version (void) { printf ("sane-desc %s (%s)\n", SANE_DESC_VERSION, PACKAGE_STRING); printf ("Copyright (C) 2002 Henning Meier-Geinitz " "\n" "sane-desc comes with NO WARRANTY, to the extent permitted by " "law.\n" "You may redistribute copies of sane-desc under the terms of the " "GNU General\n" "Public License.\n" "For more information about these matters, see the files named " "COPYING.\n"); } static SANE_Bool get_options (int argc, char **argv) { int longindex; int opt; static struct option desc_options[] = { {"search-dir", required_argument, NULL, 's'}, {"mode", required_argument, NULL, 'm'}, {"title", required_argument, NULL, 't'}, {"intro", required_argument, NULL, 'i'}, {"debug-level", required_argument, NULL, 'd'}, {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'V'}, {0, 0, 0, 0} }; while ((opt = getopt_long (argc, argv, "s:m:t:i:d:hV", desc_options, &longindex)) != -1) { switch (opt) { case 'h': print_usage (argv[0]); exit (0); case 'V': print_version (); exit (0); case 's': search_dir = strdup (optarg); DBG_INFO ("setting search directory to `%s'\n", search_dir); break; case 'm': if (strcmp (optarg, "ascii") == 0) { DBG_INFO ("Output mode: ascii\n"); mode = output_mode_ascii; } else if (strcmp (optarg, "html-backends") == 0) { DBG_INFO ("Output mode: html-backends\n"); mode = output_mode_html_backends; } else if (strcmp (optarg, "html-backends-split") == 0) { DBG_INFO ("Output mode: html-backends-split\n"); mode = output_mode_html_backends_split; } else if (strcmp (optarg, "html-mfgs") == 0) { DBG_INFO ("Output mode: html-mfgs\n"); mode = output_mode_html_mfgs; } else { DBG_ERR ("Unknown output mode: %s\n", optarg); exit (1); } break; case 't': title = optarg; DBG_INFO ("setting title to `%s'\n", optarg); break; case 'i': intro = optarg; DBG_INFO ("setting intro to `%s'\n", optarg); break; case 'd': debug = atoi (optarg); DBG_INFO ("setting debug level to %d\n", debug); break; case '?': DBG_ERR ("unknown option (use -h for help)\n"); return SANE_FALSE; case ':': DBG_ERR ("missing parameter (use -h for help)\n"); return SANE_FALSE; default: DBG_ERR ("missing option (use -h for help)\n"); return SANE_FALSE; } } if (!search_dir) search_dir = "."; return SANE_TRUE; } static int char_compare (char char1, char char2) { char1 = toupper (char1); char2 = toupper (char2); if (char1 < char2) return -1; else if (char1 > char2) return 1; else return 0; } static int num_compare (char *num_string1, char *num_string2) { int num1 = atoi (num_string1); int num2 = atoi (num_string2); if (num1 < num2) return -1; else if (num1 > num2) return 1; else return 0; } /* Compare two strings, try to sort numbers correctly (600 < 1200) */ static int string_compare (char *string1, char *string2) { int count = 0; int compare = 0; while (string1[count] && string2[count]) { if (isdigit (string1[count]) && isdigit (string2[count])) compare = num_compare (&string1[count], &string2[count]); else compare = char_compare (string1[count], string2[count]); if (compare != 0) return compare; count++; } return char_compare (string1[count], string2[count]); } /* Add URLs to the end of the list if they are unique */ static url_entry * update_url_list (url_entry * first_url, char *new_url) { url_entry *url = first_url; SANE_Bool found = SANE_FALSE; while (url && url->name) { if (string_compare (url->name, new_url) == 0) found = SANE_TRUE; url = url->next; } if (found) return first_url; url = first_url; if (url) { while (url->next) url = url->next; url->next = calloc (1, sizeof (url_entry)); url = url->next; } else { first_url = calloc (1, sizeof (url_entry)); url = first_url; } if (!url) { DBG_ERR ("update_url_list: couldn't calloc url_entry\n"); exit (1); } url->name = new_url; return first_url; } /* Get the next token, ignoring escaped quotation marks */ static const char * get_token (const char *str, char **string_const) { const char *start; size_t len; str = sanei_config_skip_whitespace (str); if (*str == '"') { start = ++str; while (*str && (*str != '"' || *(str - 1) == '\\')) ++str; len = str - start; if (*str == '"') ++str; else start = 0; /* final double quote is missing */ } else { start = str; while (*str && !isspace (*str)) ++str; len = str - start; } if (start) *string_const = strndup (start, len); else string_const = 0; return str; } /* Checks a line for a keyword token and determines keyword/string argument */ static SANE_Status read_keyword (SANE_String line, SANE_String keyword_token, parameter_type p_type, void *argument) { SANE_String_Const cp; SANE_Char *word; word = 0; cp = get_token (line, &word); if (strcmp (word, keyword_token) != 0) return SANE_STATUS_INVAL; free (word); word = 0; switch (p_type) { case param_none: return SANE_STATUS_GOOD; case param_string: { char *pos; cp = get_token (cp, &word); /* remove escaped quotations */ while ((pos = strstr (word, "\\\"")) != 0) *pos = ' '; DBG_DBG ("read_keyword: set entry `%s' to `%s'\n", keyword_token, word); *(SANE_String *) argument = strdup (word); break; } default: DBG_ERR ("read_keyword: unknown param_type %d\n", p_type); return SANE_STATUS_INVAL; } if (word) free (word); word = 0; return SANE_STATUS_GOOD; } /* Read and interprete the .desc files */ static SANE_Bool read_files (void) { struct stat stat_buf; DIR *dir; struct dirent *dir_entry; FILE *fp; char file_name[PATH_MAX]; SANE_Char line[PATH_MAX], *word; SANE_String_Const cp; backend_entry *current_backend = 0; type_entry *current_type = 0; mfg_entry *current_mfg = 0; model_entry *current_model = 0; enum level current_level = level_backend; DBG_INFO ("looking for .desc files in `%s'\n", search_dir); if (stat (search_dir, &stat_buf) < 0) { DBG_ERR ("cannot stat `%s' (%s)\n", search_dir, strerror (errno)); return SANE_FALSE; } if (!S_ISDIR (stat_buf.st_mode)) { DBG_ERR ("`%s' is not a directory\n", search_dir); return SANE_FALSE; } if ((dir = opendir (search_dir)) == 0) { DBG_ERR ("cannot read directory `%s' (%s)\n", search_dir, strerror (errno)); return SANE_FALSE; } while ((dir_entry = readdir (dir)) != NULL) { if (strlen (dir_entry->d_name) > 5 && strcmp (dir_entry->d_name + strlen (dir_entry->d_name) - 5, ".desc") == 0) { if (strlen (search_dir) + strlen (dir_entry->d_name) + 1 + 1 > PATH_MAX) { DBG_ERR ("filename too long\n"); return SANE_FALSE; } sprintf (file_name, "%s/%s", search_dir, dir_entry->d_name); DBG_INFO ("-> reading desc file: %s\n", file_name); fp = fopen (file_name, "r"); if (!fp) { DBG_ERR ("can't open desc file: %s (%s)\n", file_name, strerror (errno)); return SANE_FALSE; } current_backend = 0; current_type = 0; current_mfg = 0; current_model = 0; while (sanei_config_read (line, sizeof (line), fp)) { char *string_entry = 0; word = 0; cp = get_token (line, &word); if (!word || cp == line) { DBG_DBG ("ignoring empty line\n"); if (word) free (word); word = 0; continue; } if (word[0] == ';') { DBG_DBG ("ignoring comment line\n"); free (word); word = 0; continue; } DBG_DBG ("line: %s\n", line); if (read_keyword (line, ":backend", param_string, &string_entry) == SANE_STATUS_GOOD) { backend_entry *be = first_backend, *prev_be = 0, *new_be = 0; DBG_INFO ("creating backend entry `%s'\n", string_entry); new_be = calloc (1, sizeof (backend_entry)); if (!new_be) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } new_be->name = string_entry; new_be->status = status_unknown; new_be->new = SANE_FALSE; if (!be) { first_backend = new_be; be = new_be; } else { while (be) { int compare = string_compare (new_be->name, be->name); if (compare <= 0) { backend_entry *be_tmp = be; be = new_be; be->next = be_tmp; if (!prev_be) first_backend = be; else prev_be->next = be; break; } prev_be = be; be = be->next; } if (!be) /* last entry */ { prev_be->next = new_be; be = prev_be->next; } } current_backend = be; current_type = 0; current_mfg = 0; current_model = 0; current_level = level_backend; continue; } if (!current_backend) { DBG_ERR ("use `:backend' keyword first\n"); return SANE_FALSE; } if (read_keyword (line, ":version", param_string, &string_entry) == SANE_STATUS_GOOD) { if (current_backend->version) { DBG_WARN ("overwriting version of backend `%s' to `%s'" "(was: `%s')\n", current_backend->name, string_entry, current_backend->version, current_backend->version); } DBG_INFO ("setting version of backend `%s' to `%s'\n", current_backend->name, string_entry); current_backend->version = string_entry; continue; } if (read_keyword (line, ":status", param_string, &string_entry) == SANE_STATUS_GOOD) { switch (current_level) { case level_backend: if (current_backend->status != status_unknown) { DBG_WARN ("overwriting status of backend `%s'\n", current_backend->name); } if (strcmp (string_entry, ":new") == 0) { DBG_WARN ("ignored `%s' status :new, use keyword " "`:new :yes' instead\n", current_backend->name); current_backend->status = status_unknown; } else if (strcmp (string_entry, ":alpha") == 0) { DBG_INFO ("setting status of backend `%s' to `alpha'\n", current_backend->name); current_backend->status = status_alpha; } else if (strcmp (string_entry, ":beta") == 0) { DBG_INFO ("setting status of backend `%s' to `beta'\n", current_backend->name); current_backend->status = status_beta; } else if (strcmp (string_entry, ":stable") == 0) { DBG_INFO ("setting status of backend `%s' to `stable'\n", current_backend->name); current_backend->status = status_stable; } else { DBG_ERR ("unknown status of backend `%s': `%s'\n", current_backend->name, string_entry); current_backend->status = status_unknown; return SANE_FALSE; } break; case level_model: if (current_model->status != status_unknown) { DBG_WARN ("overwriting status of model `%s'\n", current_model->name); } if (strcmp (string_entry, ":alpha") == 0) { DBG_INFO ("setting status of model `%s' to `alpha'\n", current_model->name); current_model->status = status_alpha; } else if (strcmp (string_entry, ":beta") == 0) { DBG_INFO ("setting status of model `%s' to `beta'\n", current_model->name); current_model->status = status_beta; } else if (strcmp (string_entry, ":stable") == 0) { DBG_INFO ("setting status of model `%s' to `stable'\n", current_model->name); current_model->status = status_stable; } else if (strcmp (string_entry, ":untested") == 0) { DBG_INFO ("setting status of model `%s' to `untested'\n", current_model->name); current_model->status = status_untested; } else if (strcmp (string_entry, ":unsupported") == 0) { DBG_INFO ("setting status of model `%s' to `unsupported'\n", current_model->name); current_model->status = status_unsupported; } else { DBG_ERR ("unknown status of model `%s': `%s'\n", current_model->name, string_entry); current_model->status = status_unknown; return SANE_FALSE; } break; default: DBG_ERR ("level %d not implemented for :status\n", current_level); return SANE_FALSE; } continue; } if (read_keyword (line, ":new", param_string, &string_entry) == SANE_STATUS_GOOD) { if (strcmp (string_entry, ":yes") == 0) { DBG_INFO ("backend %s is new in this SANE release\n", current_backend->name); current_backend->new = SANE_TRUE; } else if (strcmp (string_entry, ":no") == 0) { DBG_INFO ("backend %s is NOT new in this SANE release\n", current_backend->name); current_backend->new = SANE_FALSE; } else { DBG_ERR ("unknown :new parameter of backend `%s': " "`%s'\n", current_backend->name, string_entry); current_backend->new = SANE_FALSE; return SANE_FALSE; } continue; } if (read_keyword (line, ":manpage", param_string, &string_entry) == SANE_STATUS_GOOD) { if (current_backend->manpage) { DBG_WARN ("overwriting manpage of backend `%s' to `%s'" "(was: `%s')\n", current_backend->name, string_entry, current_backend->manpage); } DBG_INFO ("setting manpage of backend `%s' to `%s'\n", current_backend->name, string_entry); current_backend->manpage = string_entry; continue; } if (read_keyword (line, ":devicetype", param_string, &string_entry) == SANE_STATUS_GOOD) { type_entry *type = 0; type = current_backend->type; DBG_INFO ("adding `%s' to list of device types of backend " "`%s'\n", string_entry, current_backend->name); if (type) { while (type->next) type = type->next; type->next = calloc (1, sizeof (type_entry)); type = type->next; } else { current_backend->type = calloc (1, sizeof (type_entry)); type = current_backend->type; } type->type = type_unknown; if (strcmp (string_entry, ":scanner") == 0) { DBG_INFO ("setting device type of backend `%s' to " "scanner\n", current_backend->name); type->type = type_scanner; } else if (strcmp (string_entry, ":stillcam") == 0) { DBG_INFO ("setting device type of backend `%s' to " "still camera\n", current_backend->name); type->type = type_stillcam; } else if (strcmp (string_entry, ":vidcam") == 0) { DBG_INFO ("setting device type of backend `%s' to " "video camera\n", current_backend->name); type->type = type_vidcam; } else if (strcmp (string_entry, ":api") == 0) { DBG_INFO ("setting device type of backend `%s' to " "API\n", current_backend->name); type->type = type_api; } else if (strcmp (string_entry, ":meta") == 0) { DBG_INFO ("setting device type of backend `%s' to " "meta\n", current_backend->name); type->type = type_meta; } else { DBG_ERR ("unknown device type of backend `%s': `%s'\n", current_backend->name, string_entry); type->type = type_unknown; return SANE_FALSE; } current_type = type; current_mfg = 0; current_model = 0; continue; } if (read_keyword (line, ":desc", param_string, &string_entry) == SANE_STATUS_GOOD) { if (!current_type) { DBG_ERR ("use `:devicetype' keyword first\n"); return SANE_FALSE; } if (current_type->type < type_meta) { DBG_ERR ("use `:desc' for `:api' and `:meta' only\n"); return SANE_FALSE; } if (current_type->desc) { DBG_WARN ("overwriting description of device type of " "backend `%s' to `%s' (was: `%s')\n", current_backend->name, string_entry, current_type->desc); } DBG_INFO ("setting description of backend `%s' to `%s'\n", current_backend->name, string_entry); current_type->desc = calloc (1, sizeof (desc_entry)); if (!current_type->desc) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } current_type->desc->desc = string_entry; current_level = level_desc; current_mfg = 0; current_model = 0; continue; } if (read_keyword (line, ":mfg", param_string, &string_entry) == SANE_STATUS_GOOD) { mfg_entry *mfg = 0; if (!current_type) { DBG_ERR ("use `:devicetype' keyword first\n"); return SANE_FALSE; } if (current_type->type >= type_meta) { DBG_ERR ("use `:mfg' for hardware devices only\n"); return SANE_FALSE; } mfg = current_type->mfg; if (mfg) { while (mfg->next) mfg = mfg->next; mfg->next = calloc (1, sizeof (mfg_entry)); mfg = mfg->next; } else { current_type->mfg = calloc (1, sizeof (mfg_entry)); mfg = current_type->mfg; } if (!mfg) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } mfg->name = string_entry; DBG_INFO ("adding mfg entry %s to backend `%s'\n", string_entry, current_backend->name); current_mfg = mfg; current_model = 0; current_level = level_mfg; continue; } if (read_keyword (line, ":model", param_string, &string_entry) == SANE_STATUS_GOOD) { model_entry *model = 0; if (!current_type) { DBG_ERR ("use `:devicetype' keyword first\n"); return SANE_FALSE; } if (current_level != level_mfg && current_level != level_model) { DBG_ERR ("use `:mfg' keyword first\n"); return SANE_FALSE; } model = current_mfg->model; if (model) { while (model->next) model = model->next; model->next = calloc (1, sizeof (model_entry)); model = model->next; } else { current_mfg->model = calloc (1, sizeof (model_entry)); model = current_mfg->model; } if (!model) { DBG_ERR ("calloc failed (%s)\n", strerror (errno)); return SANE_FALSE; } model->name = string_entry; model->status = status_unknown; DBG_INFO ("adding model entry %s to manufacturer `%s'\n", string_entry, current_mfg->name); current_model = model; current_level = level_model; continue; } if (read_keyword (line, ":interface", param_string, &string_entry) == SANE_STATUS_GOOD) { if (!current_model) { DBG_WARN ("ignored `%s' :interface, only allowed for " "hardware devices\n", current_backend->name); continue; } if (current_model->interface) { DBG_WARN ("overwriting interface of model " "`%s' to `%s' (was: `%s')\n", current_model->name, string_entry, current_model->interface); } DBG_INFO ("setting interface of model `%s' to `%s'\n", current_model->name, string_entry); current_model->interface = string_entry; continue; } if (read_keyword (line, ":url", param_string, &string_entry) == SANE_STATUS_GOOD) { switch (current_level) { case level_backend: current_backend->url = update_url_list (current_backend->url, string_entry); DBG_INFO ("adding `%s' to list of urls of backend " "`%s'\n", string_entry, current_backend->name); break; case level_mfg: current_mfg->url = update_url_list (current_mfg->url, string_entry); DBG_INFO ("adding `%s' to list of urls of mfg " "`%s'\n", string_entry, current_mfg->name); break; case level_desc: current_type->desc->url = update_url_list (current_type->desc->url, string_entry); DBG_INFO ("adding `%s' to list of urls of description " "for backend `%s'\n", string_entry, current_backend->name); break; case level_model: current_model->url = update_url_list (current_model->url, string_entry); DBG_INFO ("adding `%s' to list of urls of model " "`%s'\n", string_entry, current_model->name); break; default: DBG_ERR ("level %d not implemented for :url\n", current_level); return SANE_FALSE; } continue; } if (read_keyword (line, ":comment", param_string, &string_entry) == SANE_STATUS_GOOD) { switch (current_level) { case level_backend: current_backend->comment = string_entry; DBG_INFO ("setting comment of backend %s to `%s'\n", current_backend->name, string_entry); break; case level_mfg: current_mfg->comment = string_entry; DBG_INFO ("setting comment of manufacturer %s to `%s'\n", current_mfg->name, string_entry); break; case level_desc: current_type->desc->comment = string_entry; DBG_INFO ("setting comment of description for " "backend %s to `%s'\n", current_backend->name, string_entry); break; case level_model: current_model->comment = string_entry; DBG_INFO ("setting comment of model %s to `%s'\n", current_model->name, string_entry); break; default: DBG_ERR ("level %d not implemented for `:comment'\n", current_level); return SANE_FALSE; } continue; } DBG_ERR ("unknown keyword token in line `%s'\n", line); return SANE_FALSE; } /* while (sanei_config_readline) */ fclose (fp); } /* if (strlen) */ } /* while (direntry) */ if (!first_backend) { DBG_ERR ("Couldn't find any .desc file\n"); return SANE_FALSE; } return SANE_TRUE; } /* Create a model_record_entry based on a model_entry */ static model_record_entry * create_model_record (model_entry * model) { model_record_entry *model_record; model_record = calloc (1, sizeof (model_record_entry)); if (!model_record) { DBG_ERR ("create_model_record: couldn't calloc model_record_entry\n"); exit (1); } model_record->name = model->name; model_record->status = model->status; model_record->interface = model->interface; model_record->url = model->url; model_record->comment = model->comment; return model_record; } /* Calculate the priority of statuses: */ /* alpha, beta, stable -> 2, untested -> 1, unsupported -> 0 */ static int calc_priority (status_entry status) { switch (status) { case status_untested: return 1; case status_unsupported: return 0; default: return 2; } } /* Insert model into list at the alphabetically correct position */ static model_record_entry * update_model_record_list (model_record_entry * first_model_record, model_entry * model, backend_entry * be) { model_record_entry *model_record = first_model_record; if (!first_model_record) { /* First model for this manufacturer */ first_model_record = create_model_record (model); model_record = first_model_record; } else { model_record_entry *prev_model_record = 0; while (model_record) { int compare = string_compare (model->name, model_record->name); if (compare <= 0) { model_record_entry *tmp_model_record = model_record; if (compare == 0 && string_compare (model->interface, model_record->interface) == 0) { /* Two entries for the same model */ int new_priority = calc_priority (model->status); int old_priority = calc_priority (model_record->status); if (new_priority < old_priority) { DBG_DBG ("update_model_record_list: model %s ignored, backend %s has " "higher priority\n", model->name, model_record->be->name); return first_model_record; } if (new_priority > old_priority) { DBG_DBG ("update_model_record_list: model %s overrides the one from backend %s\n", model->name, model_record->be->name); tmp_model_record = model_record->next; } } /* correct position */ model_record = create_model_record (model); model_record->next = tmp_model_record; if (!prev_model_record) first_model_record = model_record; else prev_model_record->next = model_record; break; } prev_model_record = model_record; model_record = model_record->next; } if (!model_record) /* last entry */ { prev_model_record->next = create_model_record (model); model_record = prev_model_record->next; } } /* if (first_model_record) */ model_record->be = be; DBG_DBG ("update_model_record_list: added model %s\n", model->name); return first_model_record; } /* Insert manufacturer into list at the alphabetically correct position, */ /* create new record if neccessary */ static mfg_record_entry * update_mfg_record_list (mfg_record_entry * first_mfg_record, mfg_entry * mfg, backend_entry * be) { model_entry *model = mfg->model; mfg_record_entry *mfg_record = first_mfg_record; while (mfg_record) { if (string_compare (mfg_record->name, mfg->name) == 0) { /* Manufacturer already exists */ url_entry *mfg_url = mfg->url; /* Manufacturer comments and (additional) URLs? */ if (!mfg_record->comment) mfg_record->comment = mfg->comment; while (mfg_url && mfg_url->name) { mfg_record->url = update_url_list (mfg_record->url, mfg_url->name); mfg_url = mfg_url->next; } break; } mfg_record = mfg_record->next; } if (!mfg_record) { /* Manufacturer doesn't exist yet */ url_entry *url = mfg->url; mfg_record = calloc (1, sizeof (mfg_record_entry)); if (!mfg_record) { DBG_ERR ("update_mfg_record_list: couldn't calloc " "mfg_record_entry\n"); exit (1); } mfg_record->name = mfg->name; mfg_record->comment = mfg->comment; while (url) { mfg_record->url = update_url_list (mfg_record->url, url->name); url = url->next; } if (first_mfg_record != 0) { /* We already have one manufacturer in the list */ mfg_record_entry *new_mfg_record = mfg_record; mfg_record_entry *prev_mfg_record = 0; mfg_record = first_mfg_record; while (mfg_record) { int compare = string_compare (new_mfg_record->name, mfg_record->name); if (compare <= 0) { mfg_record_entry *tmp_mfg_record = mfg_record; mfg_record = new_mfg_record; mfg_record->next = tmp_mfg_record; if (!prev_mfg_record) first_mfg_record = mfg_record; else prev_mfg_record->next = mfg_record; break; } prev_mfg_record = mfg_record; mfg_record = mfg_record->next; } if (!mfg_record) /* last entry */ { prev_mfg_record->next = new_mfg_record; mfg_record = prev_mfg_record->next; } } else first_mfg_record = mfg_record; DBG_DBG ("update_mfg_record_list: created mfg %s\n", mfg_record->name); } /* if (!mfg_record) */ /* create model entries */ while (model) { mfg_record->model_record = update_model_record_list (mfg_record->model_record, model, be); model = model->next; } return first_mfg_record; } /* Create a sorted list of manufacturers based on the backends list */ static mfg_record_entry * create_mfg_list (device_type dev_type) { mfg_record_entry *first_mfg_record = 0; backend_entry *be = first_backend; DBG_DBG ("create_mfg_list: start\n"); while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) { mfg_entry *mfg = type->mfg; while (mfg) { first_mfg_record = update_mfg_record_list (first_mfg_record, mfg, be); mfg = mfg->next; } } type = type->next; } be = be->next; } DBG_DBG ("create_mfg_list: exit\n"); return first_mfg_record; } /* Print an ASCII list with all the information we have */ static void ascii_print_backends (void) { backend_entry *be; be = first_backend; while (be) { url_entry *url = be->url; type_entry *type = be->type; if (be->name) printf ("backend `%s'\n", be->name); else printf ("backend *none*\n"); if (be->version) printf (" version `%s'\n", be->version); else printf (" version *none*\n"); if (be->new) printf (" NEW!\n"); switch (be->status) { case status_alpha: printf (" status alpha\n"); break; case status_beta: printf (" status beta\n"); break; case status_stable: printf (" status stable\n"); break; default: printf (" status *unknown*\n"); break; } if (be->manpage) printf (" manpage `%s'\n", be->manpage); else printf (" manpage *none*\n"); if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (be->comment) printf (" comment `%s'\n", be->comment); else printf (" comment *none*\n"); if (type) while (type) { switch (type->type) { case type_scanner: printf (" type scanner\n"); break; case type_stillcam: printf (" type stillcam\n"); break; case type_vidcam: printf (" type vidcam\n"); break; case type_meta: printf (" type meta\n"); break; case type_api: printf (" type api\n"); break; default: printf (" type *unknown*\n"); break; } if (type->desc) { url_entry *url = type->desc->url; printf (" desc `%s'\n", type->desc->desc); if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (type->desc->comment) printf (" comment `%s'\n", type->desc->comment); else printf (" comment *none*\n"); } else if (type->type >= type_meta) printf (" desc *none*\n"); if (type->mfg) { mfg_entry *mfg = type->mfg; while (mfg) { model_entry *model = mfg->model; url_entry *url = mfg->url; printf (" mfg `%s'\n", mfg->name); if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (mfg->comment) printf (" comment `%s'\n", mfg->comment); else printf (" comment *none*\n"); if (model) while (model) { url_entry *url = model->url; printf (" model `%s'\n", model->name); if (model->interface) printf (" interface `%s'\n", model->interface); else printf (" interface *none*\n"); switch (model->status) { case status_alpha: printf (" status alpha\n"); break; case status_beta: printf (" status beta\n"); break; case status_stable: printf (" status stable\n"); break; case status_untested: printf (" status untested\n"); break; case status_unsupported: printf (" status unsupported\n"); break; default: printf (" status *unknown*\n"); break; } if (url) while (url) { printf (" url `%s'\n", url->name); url = url->next; } else printf (" url *none*\n"); if (model->comment) printf (" comment `%s'\n", model->comment); else printf (" comment *none*\n"); model = model->next; } else printf (" model *none*\n"); mfg = mfg->next; } /* while (mfg) */ } else if (type->type < type_meta) printf (" mfg *none*\n"); type = type->next; } /* while (type) */ else printf (" type *none*\n"); be = be->next; } /* while (be) */ } /* Generate a name used for HTML tags */ static char * html_generate_anchor_name (char *manufacturer_name) { char *name = strdup (manufacturer_name); char *pointer = name; if (!name) { DBG_DBG ("html_generate_anchor_name: couldn't strdup\n"); } while (*pointer) { if (!isalnum (*pointer)) *pointer = '-'; else *pointer = toupper (*pointer); pointer++; } return name; } /* Generate a table of all backends providing models of type dev_type */ static void html_backends_table (device_type dev_type) { backend_entry *be = first_backend; SANE_Bool found = SANE_FALSE; /* check if we have at least one matching backend */ while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) found = SANE_TRUE; type = type->next; } be = be->next; } if (!found) { printf ("

(none)

\n"); return; } be = first_backend; printf ("\n"); printf ("\n"); switch (dev_type) { case type_scanner: case type_stillcam: case type_vidcam: printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); break; case type_meta: case type_api: printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); break; default: DBG_ERR ("Unknown device type (%d)\n", dev_type); return; } printf ("\n"); while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) { mfg_entry *mfg = type->mfg; model_entry *model; int row_num = 0; /* count models for backend rowspan */ if (mfg) /* scanner, camera */ while (mfg) { model = mfg->model; while (model) { model = model->next; row_num++; } mfg = mfg->next; } else row_num = 1; printf ("\n"); if (be->manpage) printf ("\n", row_num, be->manpage, be->manpage); else printf ("\n", row_num); mfg = type->mfg; if (!mfg && type->desc) { if (type->desc->desc) { if (type->desc->url && type->desc->url->name) printf ("\n", type->desc->url->name, type->desc->desc); else printf ("\n", type->desc->desc); } else printf ("\n"); printf ("\n"); if (type->desc->comment) printf ("\n", type->desc->comment); else printf ("\n"); printf ("\n"); } while (mfg) { model = mfg->model; if (model) { int num_models = 0; while (model) /* count models for rowspan */ { model = model->next; num_models++; } model = mfg->model; if (mfg != type->mfg) printf ("\n"); printf ("\n"); if (model->url && model->url->name) printf ("\n", model->url->name, model->name); else printf ("\n", model->name); if (model->interface) printf ("\n", model->interface); else printf ("\n"); printf ("\n"); if (model->comment && model->comment[0] != 0) printf ("\n", model->comment); else printf ("\n"); model = model->next; printf ("\n"); } /* while (model) */ } /* if (num_models) */ mfg = mfg->next; } /* while (mfg) */ } /* if (type->type) */ type = type->next; } /* while (type) */ be = be->next; } /* while (be) */ printf ("
BackendManual PageSupported Devices
ManufacturerModelInterfaceStatusCommentBackendManual PageDescriptionStatusComment
\n", row_num); if (be->url && be->url->name) printf ("%s\n", be->url->name, be->name); else printf ("%s", be->name); if (be->version || be->new) { printf ("
("); if (be->version) { printf ("v%s", be->version); if (be->new) printf (", NEW!"); } else printf ("NEW!"); printf (")\n"); } printf ("
%s?%s%s "); switch (be->status) { case status_alpha: printf ("alpha"); break; case status_beta: printf ("beta"); break; case status_stable: printf ("stable"); break; default: printf ("?"); break; } printf ("%s 
\n", num_models); if (mfg->url && mfg->url->name) printf ("%s\n", mfg->url->name, mfg->name); else printf ("%s\n", mfg->name); while (model) { enum status_entry status = model->status; if (model != mfg->model) printf ("
%s%s%s?"); if (status == status_unknown) status = be->status; switch (status) { case status_alpha: printf ("alpha"); break; case status_beta: printf ("beta"); break; case status_stable: printf ("stable"); break; case status_untested: printf ("untested"); break; case status_unsupported: printf ("unsupported"); break; default: printf ("?"); break; } printf ("%s 
\n"); } /* Generate one table per backend of all backends providing models */ /* of type dev_type */ static void html_backends_split_table (device_type dev_type) { backend_entry *be = first_backend; SANE_Bool first = SANE_TRUE; printf ("

Backends: \n"); while (be) /* print link list */ { type_entry *type = be->type; SANE_Bool found = SANE_FALSE; while (type) { if (type->type == dev_type) found = SANE_TRUE; type = type->next; } if (found) { if (!first) printf (", \n"); first = SANE_FALSE; printf ("%s", html_generate_anchor_name (be->name), be->name); } be = be->next; } be = first_backend; if (first) printf ("(none)\n"); printf ("

\n"); while (be) { type_entry *type = be->type; while (type) { if (type->type == dev_type) { mfg_entry *mfg = type->mfg; model_entry *model = mfg->model; printf ("

Backend: %s\n", html_generate_anchor_name (be->name), be->name); if (be->version || be->new) { printf ("("); if (be->version) { printf ("v%s", be->version); if (be->new) printf (", NEW!"); } else printf ("NEW!"); printf (")\n"); } printf ("

\n"); printf ("

\n"); if (be->url && be->url->name) { url_entry *url = be->url; printf ("Link(s): \n"); while (url) { if (url != be->url) printf (", "); printf ("%s", url->name, url->name); url = url->next; } printf ("
\n"); } if (be->manpage) printf ("Manual page: %s
\n", be->manpage, be->manpage); if (be->comment) printf ("Comment: %s
\n", be->comment); if (type->desc) { printf ("Status: \n"); switch (be->status) { case status_alpha: printf ("alpha"); break; case status_beta: printf ("beta"); break; case status_stable: printf ("stable"); break; default: printf ("?"); break; } printf ("
\n"); if (type->desc->desc) { if (type->desc->url && type->desc->url->name) printf ("Description: " "%s
\n", type->desc->url->name, type->desc->desc); else printf ("Description: %s
\n", type->desc->desc); } if (type->desc->comment) printf ("Comment: %s
\n", type->desc->comment); printf ("

\n"); type = type->next; continue; } printf ("

\n"); if (!mfg) { type = type->next; continue; } printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); mfg = type->mfg; while (mfg) { model = mfg->model; if (model) { int num_models = 0; while (model) /* count models for rowspan */ { model = model->next; num_models++; } model = mfg->model; printf ("\n"); printf ("\n"); if (model->url && model->url->name) printf ("\n", model->url->name, model->name); else printf ("\n", model->name); if (model->interface) printf ("\n", model->interface); else printf ("\n"); printf ("\n"); if (model->comment && model->comment[0] != 0) printf ("\n", model->comment); else printf ("\n"); model = model->next; printf ("\n"); } /* while (model) */ } /* if (num_models) */ mfg = mfg->next; } /* while (mfg) */ } /* if (type->type) */ printf ("
ManufacturerModelInterfaceStatusComment
\n", num_models); if (mfg->url && mfg->url->name) printf ("%s\n", mfg->url->name, mfg->name); else printf ("%s\n", mfg->name); while (model) { enum status_entry status = model->status; if (model != mfg->model) printf ("
%s%s%s?"); if (status == status_unknown) status = be->status; switch (status) { case status_alpha: printf ("alpha"); break; case status_beta: printf ("beta"); break; case status_stable: printf ("stable"); break; case status_untested: printf ("untested"); break; case status_unsupported: printf ("unsupported"); break; default: printf ("?"); break; } printf ("%s 
\n"); type = type->next; } /* while (type) */ be = be->next; } /* while (be) */ printf ("\n"); } /* Generate one table per manufacturer constructed of all backends */ /* providing models of type dev_type */ static void html_mfgs_table (device_type dev_type) { mfg_record_entry *mfg_record = 0, *first_mfg_record = 0; first_mfg_record = create_mfg_list (dev_type); mfg_record = first_mfg_record; printf ("

Manufacturers: \n"); while (mfg_record) { if (mfg_record != first_mfg_record) printf (", \n"); printf ("%s", html_generate_anchor_name (mfg_record->name), mfg_record->name); mfg_record = mfg_record->next; } mfg_record = first_mfg_record; if (!mfg_record) printf ("(none)\n"); printf ("

\n"); while (mfg_record) { model_record_entry *model_record = mfg_record->model_record; printf ("

Manufacturer: %s

\n", html_generate_anchor_name (mfg_record->name), mfg_record->name); printf ("

\n"); if (mfg_record->url && mfg_record->url->name) { url_entry *url = mfg_record->url; printf ("Link(s): \n"); while (url) { if (url != mfg_record->url) printf (", "); printf ("%s", url->name, url->name); url = url->next; } printf ("
\n"); } if (mfg_record->comment) printf ("Comment: %s
\n", mfg_record->comment); printf ("

\n"); if (!model_record) { mfg_record = mfg_record->next; continue; } printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); printf ("\n"); while (model_record) { enum status_entry status = model_record->status; if (model_record->url && model_record->url->name) printf ("\n", model_record->url->name, model_record->name); else printf ("\n", model_record->name); if (model_record->interface) printf ("\n", model_record->interface); else printf ("\n"); printf ("\n"); if (model_record->comment && model_record->comment[0] != 0) printf ("\n", model_record->comment); else printf ("\n"); printf ("\n"); if (model_record->be->manpage) printf ("\n", model_record->be->manpage, model_record->be->manpage); else printf ("\n"); printf ("\n"); model_record = model_record->next; } /* while model_record */ printf ("
ModelInterfaceStatusCommentBackendManpage
%s%s%s?"); if (status == status_unknown) status = model_record->be->status; switch (status) { case status_alpha: printf ("alpha"); break; case status_beta: printf ("beta"); break; case status_stable: printf ("stable"); break; case status_untested: printf ("untested"); break; case status_unsupported: printf ("unsupported"); break; default: printf ("?"); break; } printf ("%s \n"); if (model_record->be->url && model_record->be->url->name) printf ("%s\n", model_record->be->url->name, model_record->be->name); else printf ("%s", model_record->be->name); if (model_record->be->version || model_record->be->new) { printf ("
("); if (model_record->be->version) { printf ("v%s", model_record->be->version); if (model_record->be->new) printf (", NEW!"); } else printf ("NEW!"); printf (")\n"); } printf ("
%s?
\n"); mfg_record = mfg_record->next; } /* while mfg_record */ } /* Print the HTML headers and an introduction */ static void html_print_header (void) { printf ("\n" " \n" "\n"); printf ("%s\n", title); printf ("\n" "\n" "
\n" "\"SANE\"\n"); printf ("

%s

\n", title); printf ("
\n" "
\n"); printf ("%s\n", intro); printf ("

This is only a summary!\n" "Please consult the manpages and the author-supplied webpages\n" "for more detailed (and usually important) information\n" "concerning each backend.

\n" "

There are special tables for parallel port and \n" "USB scanners from \n" "Jonathan Buzzard.

\n"); printf ("

If you have new information or corrections, please send\n" "e-mail to sane-devel, the SANE mailing\n" "list.

\n" "

For an explanation of the tables, see the\n" "legend.\n"); printf ("

There are tables for scanners,\n" "still cameras,\n" "video cameras,\n" "APIs, and\n" "meta backends.\n"); } /* Print the HTML footers and contact information */ static void html_print_footer (void) { time_t current_time = time (0); printf ("


\n" "[Back]\n" "
\n" "sane-devel@mostang.com / SANE Development mailing list\n" "
\n" "\n"); printf ("This page was last updated on %s\n", asctime (localtime (¤t_time))); printf ("\n"); printf (" \n"); } /* Print the HTML page with one table of backends per type */ static void html_print_backends (void) { if (!title) title = "SANE: Backends (Drivers)"; if (!intro) intro = "

The following table summarizes the backends/drivers " "distributed with the latest version of sane-backends, and the hardware " "or software they support.

"; html_print_header (); printf ("

\n"); printf ("

Scanners

\n"); html_backends_table (type_scanner); printf ("

Still Cameras

\n"); html_backends_table (type_stillcam); printf ("

Video Cameras

\n"); html_backends_table (type_vidcam); printf ("

APIs

\n"); html_backends_table (type_api); printf ("

Meta Backends

\n"); html_backends_table (type_meta); printf ("
\n"); printf ("

Legend:

\n" "
\n" "
\n" "
Backend:
\n" "
Name of the backend, with a link to more extensive and\n" " detailed information, if it exists, or the email address\n" " of the author or maintainer. In parentheses if available:\n" " Version of backend/driver; newer versions may be\n" " available from their home sites.
" " NEW! means brand-new to the\n" " current release of SANE.\n" "
\n"); printf ("
Manual Page:
\n" "
A link to the man-page on-line, if it exists.
\n" "
Supported Devices (for hardware devices):
\n" "
Which hardware the backend supports.
\n" "
Manufacturer:
\n" "
Manufacturer, vendor or brand name of the device.
\n" "
Model:
\n" "
Name of the the device.
\n" "
Interface:
\n" "
How the device is connected to the computer.
\n"); printf ("
Status:
\n" "
A vague indication of robustness and reliability.\n" "
  • unsupported" " means the device is not supported at least by this backend. " " It may be supported by other backends, however.\n" "
  • untested means the " " device may be supported but couldn't be tested. Be very " " careful.\n"); printf ("
  • alpha means it must\n" " do something, but is not very well tested, probably has\n" " bugs, and may even crash your system, etc., etc.\n" "
  • beta means it works\n" " pretty well, and looks stable and functional, but not\n" " bullet-proof.\n" "
  • stable means someone is\n" " pulling your leg.\n" "
\n"); printf ("
Comment:
\n" "
More information about the level of support and\n" " possible problems.
\n" "
Description (for API and meta backends):
\n" "
The scope of application of the backend.\n" "
\n" "
\n"); html_print_footer (); } /* Print the HTML page with one table of models per backend */ static void html_print_backends_split (void) { if (!title) title = "SANE: Backends (Drivers)"; if (!intro) intro = "

The following table summarizes the backends/drivers " "distributed with the latest version of sane-backends, and the hardware " "or software they support.

"; html_print_header (); printf ("

Scanners

\n"); html_backends_split_table (type_scanner); printf ("

Still Cameras

\n"); html_backends_split_table (type_stillcam); printf ("

Video Cameras

\n"); html_backends_split_table (type_vidcam); printf ("

APIs

\n"); html_backends_split_table (type_api); printf ("

Meta Backends

\n"); html_backends_split_table (type_meta); printf ("

Legend:

\n" "
\n" "
\n" "
Backend:
\n" "
Name of the backend, In parentheses if available:\n" " Version of backend/driver; newer versions may be\n" " available from their home sites.
" " NEW! means brand-new to the\n" " current release of SANE.\n" "
\n" "
Link(s):
\n" "
Link(s) to more extensive and\n" " detailed information, if it exists, or the email address\n" " of the author or maintainer.\n"); printf ("
Manual Page:
\n" "
A link to the man-page on-line, if it exists.
\n" "
Comment:
\n" "
More information about the backend or model, e.g. the level of " " support and possible problems.
\n" "
Manufacturer:
\n" "
Manufacturer, vendor or brand name of the device.
\n" "
Model:
\n" "
Name of the the device.
\n" "
Interface:
\n" "
How the device is connected to the computer.
\n"); printf ("
Status:
\n" "
A vague indication of robustness and reliability.\n" "
  • unsupported" " means the device is not supported at least by this backend. " " It may be supported by other backends, however.\n"); printf ("
  • untested means the " " device may be supported but couldn't be tested. Be very " " careful.\n" "
  • alpha means it must\n" " do something, but is not very well tested, probably has\n" " bugs, and may even crash your system, etc., etc.\n"); printf ("
  • beta means it works\n" " pretty well, and looks stable and functional, but not\n" " bullet-proof.\n" "
  • stable means someone is\n" " pulling your leg.\n" "
\n"); printf ("
Description:
\n" "
The scope of application of the backend.\n" "
\n" "
\n"); html_print_footer (); } /* Print the HTML page with one table of models per manufacturer */ static void html_print_mfgs (void) { if (!title) title = "SANE: Supported Devices"; if (!intro) intro = "

The following table summarizes the devices supported " "by the latest version of sane-backends.

"; html_print_header (); printf ("

Scanners

\n"); html_mfgs_table (type_scanner); printf ("

Still Cameras

\n"); html_mfgs_table (type_stillcam); printf ("

Video Cameras

\n"); html_mfgs_table (type_vidcam); printf ("

APIs

\n"); html_backends_split_table (type_api); printf ("

Meta Backends

\n"); html_backends_split_table (type_meta); printf ("

Legend:

\n" "
\n" "
\n" "
Model:
\n" "
Name of the the device.
\n" "
Interface:
\n" "
How the device is connected to the computer.
\n" "
Status:
\n"); printf ("
A vague indication of robustness and reliability.\n" "
  • unsupported" " means the device is not supported at least by this backend. " " It may be supported by other backends, however.\n" "
  • untested means the " " device may be supported but couldn't be tested. Be very " " careful.\n"); printf ("
  • alpha means it must\n" " do something, but is not very well tested, probably has\n" " bugs, and may even crash your system, etc., etc.\n" "
  • beta means it works\n" " pretty well, and looks stable and functional, but not\n" " bullet-proof.\n" "
  • stable means someone is\n" " pulling your leg.\n" "
\n"); printf ("
Comment:
\n" "
More information about the level of support and\n" " possible problems.
\n" "
Backend:
\n" "
Name of the backend, with a link to more extensive and\n" " detailed information, if it exists, or the email address\n" " of the author or maintainer. In parentheses if available:\n" " Version of backend/driver; newer versions may be\n" " available from their home sites.
\n"); printf (" NEW! means brand-new to the\n" " current release of SANE.\n" "
\n" "
Manual Page:
\n" "
A link to the man-page on-line, if it exists.
\n" "
Description:
\n" "
The scope of application of the backend.\n" "
\n" "
\n"); html_print_footer (); } int main (int argc, char **argv) { program_name = strrchr (argv[0], '/'); if (program_name) ++program_name; else program_name = argv[0]; if (!get_options (argc, argv)) return 1; if (!read_files ()) return 1; switch (mode) { case output_mode_ascii: ascii_print_backends (); break; case output_mode_html_backends: html_print_backends (); break; case output_mode_html_backends_split: html_print_backends_split (); break; case output_mode_html_mfgs: html_print_mfgs (); break; default: DBG_ERR ("Unknown output mode\n"); return 1; } return 0; }