Git fork
at reftables-rust 735 lines 19 kB view raw
1/* 2 * Builtin help command 3 */ 4 5#define USE_THE_REPOSITORY_VARIABLE 6 7#include "builtin.h" 8#include "config.h" 9#include "environment.h" 10#include "exec-cmd.h" 11#include "gettext.h" 12#include "pager.h" 13#include "parse-options.h" 14#include "path.h" 15#include "run-command.h" 16#include "config-list.h" 17#include "help.h" 18#include "alias.h" 19#include "setup.h" 20 21#ifndef DEFAULT_HELP_FORMAT 22#define DEFAULT_HELP_FORMAT "man" 23#endif 24 25static struct man_viewer_list { 26 struct man_viewer_list *next; 27 char name[FLEX_ARRAY]; 28} *man_viewer_list; 29 30static struct man_viewer_info_list { 31 struct man_viewer_info_list *next; 32 const char *info; 33 char name[FLEX_ARRAY]; 34} *man_viewer_info_list; 35 36enum help_format { 37 HELP_FORMAT_NONE, 38 HELP_FORMAT_MAN, 39 HELP_FORMAT_INFO, 40 HELP_FORMAT_WEB 41}; 42 43enum show_config_type { 44 SHOW_CONFIG_HUMAN, 45 SHOW_CONFIG_VARS, 46 SHOW_CONFIG_SECTIONS, 47}; 48 49static enum help_action { 50 HELP_ACTION_ALL = 1, 51 HELP_ACTION_GUIDES, 52 HELP_ACTION_CONFIG, 53 HELP_ACTION_USER_INTERFACES, 54 HELP_ACTION_DEVELOPER_INTERFACES, 55 HELP_ACTION_CONFIG_FOR_COMPLETION, 56 HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, 57} cmd_mode; 58 59static char *html_path; 60static int verbose = 1; 61static enum help_format help_format = HELP_FORMAT_NONE; 62static int exclude_guides; 63static int show_external_commands = -1; 64static int show_aliases = -1; 65static struct option builtin_help_options[] = { 66 OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"), 67 HELP_ACTION_ALL), 68 OPT_BOOL(0, "external-commands", &show_external_commands, 69 N_("show external commands in --all")), 70 OPT_BOOL(0, "aliases", &show_aliases, N_("show aliases in --all")), 71 OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), 72 OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), 73 OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), 74 HELP_FORMAT_WEB), 75 OPT_SET_INT('i', "info", &help_format, N_("show info page"), 76 HELP_FORMAT_INFO), 77 OPT__VERBOSE(&verbose, N_("print command description")), 78 79 OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"), 80 HELP_ACTION_GUIDES), 81 OPT_CMDMODE(0, "user-interfaces", &cmd_mode, 82 N_("print list of user-facing repository, command and file interfaces"), 83 HELP_ACTION_USER_INTERFACES), 84 OPT_CMDMODE(0, "developer-interfaces", &cmd_mode, 85 N_("print list of file formats, protocols and other developer interfaces"), 86 HELP_ACTION_DEVELOPER_INTERFACES), 87 OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"), 88 HELP_ACTION_CONFIG), 89 OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "", 90 HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN), 91 OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "", 92 HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN), 93 94 OPT_END(), 95}; 96 97static const char * const builtin_help_usage[] = { 98 "git help [-a|--all] [--[no-]verbose] [--[no-]external-commands] [--[no-]aliases]", 99 N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>|<doc>]"), 100 "git help [-g|--guides]", 101 "git help [-c|--config]", 102 "git help [--user-interfaces]", 103 "git help [--developer-interfaces]", 104 NULL 105}; 106 107struct slot_expansion { 108 const char *prefix; 109 const char *placeholder; 110 void (*fn)(struct string_list *list, const char *prefix); 111 int found; 112}; 113 114static void list_config_help(enum show_config_type type) 115{ 116 struct slot_expansion slot_expansions[] = { 117 { "advice", "*", list_config_advices }, 118 { "color.branch", "<slot>", list_config_color_branch_slots }, 119 { "color.decorate", "<slot>", list_config_color_decorate_slots }, 120 { "color.diff", "<slot>", list_config_color_diff_slots }, 121 { "color.grep", "<slot>", list_config_color_grep_slots }, 122 { "color.interactive", "<slot>", list_config_color_interactive_slots }, 123 { "color.remote", "<slot>", list_config_color_sideband_slots }, 124 { "color.status", "<slot>", list_config_color_status_slots }, 125 { "fsck", "<msg-id>", list_config_fsck_msg_ids }, 126 { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids }, 127 { NULL, NULL, NULL } 128 }; 129 const char **p; 130 struct slot_expansion *e; 131 struct string_list keys = STRING_LIST_INIT_DUP; 132 struct string_list keys_uniq = STRING_LIST_INIT_DUP; 133 struct string_list_item *item; 134 135 for (p = config_name_list; *p; p++) { 136 const char *var = *p; 137 struct strbuf sb = STRBUF_INIT; 138 139 for (e = slot_expansions; e->prefix; e++) { 140 141 strbuf_reset(&sb); 142 strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); 143 if (!strcasecmp(var, sb.buf)) { 144 e->fn(&keys, e->prefix); 145 e->found++; 146 break; 147 } 148 } 149 strbuf_release(&sb); 150 if (!e->prefix) 151 string_list_append(&keys, var); 152 } 153 154 for (e = slot_expansions; e->prefix; e++) 155 if (!e->found) 156 BUG("slot_expansion %s.%s is not used", 157 e->prefix, e->placeholder); 158 159 string_list_sort(&keys); 160 for (size_t i = 0; i < keys.nr; i++) { 161 const char *var = keys.items[i].string; 162 const char *wildcard, *tag, *cut; 163 const char *dot = NULL; 164 struct strbuf sb = STRBUF_INIT; 165 166 switch (type) { 167 case SHOW_CONFIG_HUMAN: 168 puts(var); 169 continue; 170 case SHOW_CONFIG_SECTIONS: 171 dot = strchr(var, '.'); 172 break; 173 case SHOW_CONFIG_VARS: 174 break; 175 } 176 wildcard = strchr(var, '*'); 177 tag = strchr(var, '<'); 178 179 if (!dot && !wildcard && !tag) { 180 string_list_append(&keys_uniq, var); 181 continue; 182 } 183 184 if (dot) 185 cut = dot; 186 else if (wildcard && !tag) 187 cut = wildcard; 188 else if (!wildcard && tag) 189 cut = tag; 190 else 191 cut = wildcard < tag ? wildcard : tag; 192 193 strbuf_add(&sb, var, cut - var); 194 string_list_append(&keys_uniq, sb.buf); 195 strbuf_release(&sb); 196 197 } 198 string_list_clear(&keys, 0); 199 string_list_remove_duplicates(&keys_uniq, 0); 200 for_each_string_list_item(item, &keys_uniq) 201 puts(item->string); 202 string_list_clear(&keys_uniq, 0); 203} 204 205static enum help_format parse_help_format(const char *format) 206{ 207 if (!strcmp(format, "man")) 208 return HELP_FORMAT_MAN; 209 if (!strcmp(format, "info")) 210 return HELP_FORMAT_INFO; 211 if (!strcmp(format, "web") || !strcmp(format, "html")) 212 return HELP_FORMAT_WEB; 213 /* 214 * Please update _repo_config() in git-completion.bash when you 215 * add new help formats. 216 */ 217 die(_("unrecognized help format '%s'"), format); 218} 219 220static const char *get_man_viewer_info(const char *name) 221{ 222 struct man_viewer_info_list *viewer; 223 224 for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) 225 { 226 if (!strcasecmp(name, viewer->name)) 227 return viewer->info; 228 } 229 return NULL; 230} 231 232static int check_emacsclient_version(void) 233{ 234 struct strbuf buffer = STRBUF_INIT; 235 struct child_process ec_process = CHILD_PROCESS_INIT; 236 int version; 237 238 /* emacsclient prints its version number on stderr */ 239 strvec_pushl(&ec_process.args, "emacsclient", "--version", NULL); 240 ec_process.err = -1; 241 ec_process.stdout_to_stderr = 1; 242 if (start_command(&ec_process)) 243 return error(_("Failed to start emacsclient.")); 244 245 strbuf_read(&buffer, ec_process.err, 20); 246 close(ec_process.err); 247 248 /* 249 * Don't bother checking return value, because "emacsclient --version" 250 * seems to always exits with code 1. 251 */ 252 finish_command(&ec_process); 253 254 if (!starts_with(buffer.buf, "emacsclient")) { 255 strbuf_release(&buffer); 256 return error(_("Failed to parse emacsclient version.")); 257 } 258 259 strbuf_remove(&buffer, 0, strlen("emacsclient")); 260 version = atoi(buffer.buf); 261 262 if (version < 22) { 263 strbuf_release(&buffer); 264 return error(_("emacsclient version '%d' too old (< 22)."), 265 version); 266 } 267 268 strbuf_release(&buffer); 269 return 0; 270} 271 272static void exec_woman_emacs(const char *path, const char *page) 273{ 274 if (!check_emacsclient_version()) { 275 /* This works only with emacsclient version >= 22. */ 276 struct strbuf man_page = STRBUF_INIT; 277 278 if (!path) 279 path = "emacsclient"; 280 strbuf_addf(&man_page, "(woman \"%s\")", page); 281 execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); 282 warning_errno(_("failed to exec '%s'"), path); 283 strbuf_release(&man_page); 284 } 285} 286 287static void exec_man_konqueror(const char *path, const char *page) 288{ 289 const char *display = getenv("DISPLAY"); 290 if (display && *display) { 291 struct strbuf man_page = STRBUF_INIT; 292 const char *filename = "kfmclient"; 293 294 /* It's simpler to launch konqueror using kfmclient. */ 295 if (path) { 296 size_t len; 297 if (strip_suffix(path, "/konqueror", &len)) 298 path = xstrfmt("%.*s/kfmclient", (int)len, path); 299 filename = basename((char *)path); 300 } else 301 path = "kfmclient"; 302 strbuf_addf(&man_page, "man:%s(1)", page); 303 execlp(path, filename, "newTab", man_page.buf, (char *)NULL); 304 warning_errno(_("failed to exec '%s'"), path); 305 strbuf_release(&man_page); 306 } 307} 308 309static void exec_man_man(const char *path, const char *page) 310{ 311 if (!path) 312 path = "man"; 313 execlp(path, "man", page, (char *)NULL); 314 warning_errno(_("failed to exec '%s'"), path); 315} 316 317static void exec_man_cmd(const char *cmd, const char *page) 318{ 319 struct strbuf shell_cmd = STRBUF_INIT; 320 strbuf_addf(&shell_cmd, "%s %s", cmd, page); 321 execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); 322 warning(_("failed to exec '%s'"), cmd); 323 strbuf_release(&shell_cmd); 324} 325 326static void add_man_viewer(const char *name) 327{ 328 struct man_viewer_list **p = &man_viewer_list; 329 330 while (*p) 331 p = &((*p)->next); 332 FLEX_ALLOC_STR(*p, name, name); 333} 334 335static int supported_man_viewer(const char *name, size_t len) 336{ 337 return (!strncasecmp("man", name, len) || 338 !strncasecmp("woman", name, len) || 339 !strncasecmp("konqueror", name, len)); 340} 341 342static void do_add_man_viewer_info(const char *name, 343 size_t len, 344 const char *value) 345{ 346 struct man_viewer_info_list *new_man_viewer; 347 FLEX_ALLOC_MEM(new_man_viewer, name, name, len); 348 new_man_viewer->info = xstrdup(value); 349 new_man_viewer->next = man_viewer_info_list; 350 man_viewer_info_list = new_man_viewer; 351} 352 353static int add_man_viewer_path(const char *name, 354 size_t len, 355 const char *value) 356{ 357 if (supported_man_viewer(name, len)) 358 do_add_man_viewer_info(name, len, value); 359 else 360 warning(_("'%s': path for unsupported man viewer.\n" 361 "Please consider using 'man.<tool>.cmd' instead."), 362 name); 363 364 return 0; 365} 366 367static int add_man_viewer_cmd(const char *name, 368 size_t len, 369 const char *value) 370{ 371 if (supported_man_viewer(name, len)) 372 warning(_("'%s': cmd for supported man viewer.\n" 373 "Please consider using 'man.<tool>.path' instead."), 374 name); 375 else 376 do_add_man_viewer_info(name, len, value); 377 378 return 0; 379} 380 381static int add_man_viewer_info(const char *var, const char *value) 382{ 383 const char *name, *subkey; 384 size_t namelen; 385 386 if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name) 387 return 0; 388 389 if (!strcmp(subkey, "path")) { 390 if (!value) 391 return config_error_nonbool(var); 392 return add_man_viewer_path(name, namelen, value); 393 } 394 if (!strcmp(subkey, "cmd")) { 395 if (!value) 396 return config_error_nonbool(var); 397 return add_man_viewer_cmd(name, namelen, value); 398 } 399 400 return 0; 401} 402 403static int git_help_config(const char *var, const char *value, 404 const struct config_context *ctx, void *cb) 405{ 406 if (!strcmp(var, "help.format")) { 407 if (!value) 408 return config_error_nonbool(var); 409 help_format = parse_help_format(value); 410 return 0; 411 } 412 if (!strcmp(var, "help.htmlpath")) { 413 if (!value) 414 return config_error_nonbool(var); 415 free(html_path); 416 html_path = xstrdup(value); 417 return 0; 418 } 419 if (!strcmp(var, "man.viewer")) { 420 if (!value) 421 return config_error_nonbool(var); 422 add_man_viewer(value); 423 return 0; 424 } 425 if (starts_with(var, "man.")) 426 return add_man_viewer_info(var, value); 427 428 return git_default_config(var, value, ctx, cb); 429} 430 431static struct cmdnames main_cmds, other_cmds; 432 433static int is_git_command(const char *s) 434{ 435 if (is_builtin(s)) 436 return 1; 437 438 load_command_list("git-", &main_cmds, &other_cmds); 439 return is_in_cmdlist(&main_cmds, s) || 440 is_in_cmdlist(&other_cmds, s); 441} 442 443static const char *cmd_to_page(const char *git_cmd) 444{ 445 if (!git_cmd) 446 return "git"; 447 else if (starts_with(git_cmd, "git")) 448 return git_cmd; 449 else if (is_git_command(git_cmd)) 450 return xstrfmt("git-%s", git_cmd); 451 else if (!strcmp("scalar", git_cmd)) 452 return xstrdup(git_cmd); 453 else 454 return xstrfmt("git%s", git_cmd); 455} 456 457static void setup_man_path(void) 458{ 459 struct strbuf new_path = STRBUF_INIT; 460 const char *old_path = getenv("MANPATH"); 461 char *git_man_path = system_path(GIT_MAN_PATH); 462 463 /* We should always put ':' after our path. If there is no 464 * old_path, the ':' at the end will let 'man' to try 465 * system-wide paths after ours to find the manual page. If 466 * there is old_path, we need ':' as delimiter. */ 467 strbuf_addstr(&new_path, git_man_path); 468 strbuf_addch(&new_path, ':'); 469 if (old_path) 470 strbuf_addstr(&new_path, old_path); 471 472 free(git_man_path); 473 setenv("MANPATH", new_path.buf, 1); 474 475 strbuf_release(&new_path); 476} 477 478static void exec_viewer(const char *name, const char *page) 479{ 480 const char *info = get_man_viewer_info(name); 481 482 if (!strcasecmp(name, "man")) 483 exec_man_man(info, page); 484 else if (!strcasecmp(name, "woman")) 485 exec_woman_emacs(info, page); 486 else if (!strcasecmp(name, "konqueror")) 487 exec_man_konqueror(info, page); 488 else if (info) 489 exec_man_cmd(info, page); 490 else 491 warning(_("'%s': unknown man viewer."), name); 492} 493 494static void show_man_page(const char *page) 495{ 496 struct man_viewer_list *viewer; 497 const char *fallback = getenv("GIT_MAN_VIEWER"); 498 499 setup_man_path(); 500 for (viewer = man_viewer_list; viewer; viewer = viewer->next) 501 { 502 exec_viewer(viewer->name, page); /* will return when unable */ 503 } 504 if (fallback) 505 exec_viewer(fallback, page); 506 exec_viewer("man", page); 507 die(_("no man viewer handled the request")); 508} 509 510static void show_info_page(const char *page) 511{ 512 setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); 513 execlp("info", "info", "gitman", page, (char *)NULL); 514 die(_("no info viewer handled the request")); 515} 516 517static void get_html_page_path(struct strbuf *page_path, const char *page) 518{ 519 struct stat st; 520 const char *path = html_path; 521 char *to_free = NULL; 522 523 if (!path) 524 path = to_free = system_path(GIT_HTML_PATH); 525 526 /* 527 * Check that the page we're looking for exists. 528 */ 529 if (!strstr(path, "://")) { 530 if (stat(mkpath("%s/%s.html", path, page), &st) 531 || !S_ISREG(st.st_mode)) 532 die("'%s/%s.html': documentation file not found.", 533 path, page); 534 } 535 536 strbuf_init(page_path, 0); 537 strbuf_addf(page_path, "%s/%s.html", path, page); 538 free(to_free); 539} 540 541static void open_html(const char *path) 542{ 543 execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); 544} 545 546static void show_html_page(const char *page) 547{ 548 struct strbuf page_path; /* it leaks but we exec below */ 549 550 get_html_page_path(&page_path, page); 551 552 open_html(page_path.buf); 553} 554 555static char *check_git_cmd(const char *cmd) 556{ 557 char *alias; 558 559 if (is_git_command(cmd)) 560 return xstrdup(cmd); 561 562 alias = alias_lookup(cmd); 563 if (alias) { 564 const char **argv; 565 int count; 566 567 /* 568 * handle_builtin() in git.c rewrites "git cmd --help" 569 * to "git help --exclude-guides cmd", so we can use 570 * exclude_guides to distinguish "git cmd --help" from 571 * "git help cmd". In the latter case, or if cmd is an 572 * alias for a shell command, just print the alias 573 * definition. 574 */ 575 if (!exclude_guides || alias[0] == '!') { 576 printf_ln(_("'%s' is aliased to '%s'"), cmd, alias); 577 free(alias); 578 exit(0); 579 } 580 /* 581 * Otherwise, we pretend that the command was "git 582 * word0 --help". We use split_cmdline() to get the 583 * first word of the alias, to ensure that we use the 584 * same rules as when the alias is actually 585 * used. split_cmdline() modifies alias in-place. 586 */ 587 fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias); 588 count = split_cmdline(alias, &argv); 589 if (count < 0) 590 die(_("bad alias.%s string: %s"), cmd, 591 split_cmdline_strerror(count)); 592 free(argv); 593 return alias; 594 } 595 596 if (exclude_guides) 597 return help_unknown_cmd(cmd); 598 599 return xstrdup(cmd); 600} 601 602static void no_help_format(const char *opt_mode, enum help_format fmt) 603{ 604 const char *opt_fmt; 605 606 switch (fmt) { 607 case HELP_FORMAT_NONE: 608 return; 609 case HELP_FORMAT_MAN: 610 opt_fmt = "--man"; 611 break; 612 case HELP_FORMAT_INFO: 613 opt_fmt = "--info"; 614 break; 615 case HELP_FORMAT_WEB: 616 opt_fmt = "--web"; 617 break; 618 default: 619 BUG("unreachable"); 620 } 621 622 usage_msg_optf(_("options '%s' and '%s' cannot be used together"), 623 builtin_help_usage, builtin_help_options, opt_mode, 624 opt_fmt); 625} 626 627static void opt_mode_usage(int argc, const char *opt_mode, 628 enum help_format fmt) 629{ 630 if (argc) 631 usage_msg_optf(_("the '%s' option doesn't take any non-option arguments"), 632 builtin_help_usage, builtin_help_options, 633 opt_mode); 634 635 no_help_format(opt_mode, fmt); 636} 637 638int cmd_help(int argc, 639 const char **argv, 640 const char *prefix, 641 struct repository *repo UNUSED) 642{ 643 int nongit; 644 enum help_format parsed_help_format; 645 char *command = NULL; 646 const char *page; 647 648 argc = parse_options(argc, argv, prefix, builtin_help_options, 649 builtin_help_usage, 0); 650 parsed_help_format = help_format; 651 652 if (cmd_mode != HELP_ACTION_ALL && 653 (show_external_commands >= 0 || 654 show_aliases >= 0)) 655 usage_msg_opt(_("the '--no-[external-commands|aliases]' options can only be used with '--all'"), 656 builtin_help_usage, builtin_help_options); 657 658 switch (cmd_mode) { 659 case HELP_ACTION_ALL: 660 opt_mode_usage(argc, "--all", help_format); 661 if (verbose) { 662 setup_pager(the_repository); 663 list_all_cmds_help(show_external_commands, 664 show_aliases); 665 return 0; 666 } 667 printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); 668 load_command_list("git-", &main_cmds, &other_cmds); 669 list_commands(&main_cmds, &other_cmds); 670 printf("%s\n", _(git_more_info_string)); 671 break; 672 case HELP_ACTION_GUIDES: 673 opt_mode_usage(argc, "--guides", help_format); 674 list_guides_help(); 675 printf("%s\n", _(git_more_info_string)); 676 return 0; 677 case HELP_ACTION_CONFIG_FOR_COMPLETION: 678 opt_mode_usage(argc, "--config-for-completion", help_format); 679 list_config_help(SHOW_CONFIG_VARS); 680 return 0; 681 case HELP_ACTION_USER_INTERFACES: 682 opt_mode_usage(argc, "--user-interfaces", help_format); 683 list_user_interfaces_help(); 684 return 0; 685 case HELP_ACTION_DEVELOPER_INTERFACES: 686 opt_mode_usage(argc, "--developer-interfaces", help_format); 687 list_developer_interfaces_help(); 688 return 0; 689 case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: 690 opt_mode_usage(argc, "--config-sections-for-completion", 691 help_format); 692 list_config_help(SHOW_CONFIG_SECTIONS); 693 return 0; 694 case HELP_ACTION_CONFIG: 695 opt_mode_usage(argc, "--config", help_format); 696 setup_pager(the_repository); 697 list_config_help(SHOW_CONFIG_HUMAN); 698 printf("\n%s\n", _("'git help config' for more information")); 699 return 0; 700 } 701 702 if (!argv[0]) { 703 printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); 704 list_common_cmds_help(); 705 printf("\n%s\n", _(git_more_info_string)); 706 return 0; 707 } 708 709 setup_git_directory_gently(&nongit); 710 repo_config(the_repository, git_help_config, NULL); 711 712 if (parsed_help_format != HELP_FORMAT_NONE) 713 help_format = parsed_help_format; 714 if (help_format == HELP_FORMAT_NONE) 715 help_format = parse_help_format(DEFAULT_HELP_FORMAT); 716 717 command = check_git_cmd(argv[0]); 718 719 page = cmd_to_page(command); 720 switch (help_format) { 721 case HELP_FORMAT_NONE: 722 case HELP_FORMAT_MAN: 723 show_man_page(page); 724 break; 725 case HELP_FORMAT_INFO: 726 show_info_page(page); 727 break; 728 case HELP_FORMAT_WEB: 729 show_html_page(page); 730 break; 731 } 732 733 free(command); 734 return 0; 735}