/* Example program for GNU libtextstyle. Copyright (C) 2018-2019 Free Software Foundation, Inc. Written by Bruno Haible , 2018. 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 3 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, see . */ /* Source code of the C program. */ #include #include #include #include #include #include #include #include "names.c" /* Returns the record for a given first name, or NULL if not found. */ static const struct first_name * find_first_name (const char *name) { size_t i = 0; size_t j = sizeof (names) / sizeof (names[0]); /* Loop invariants: 1. j > i, 2. If name is in the table, then at an index >= i, < j. */ while (j - i > 1) { size_t k = i + (j - i) / 2; if (strcmp (names[k].name, name) <= 0) i = k; else j = k; } /* Here j = i + 1. */ if (strcmp (names[i].name, name) == 0) return &names[i]; else return NULL; } enum sex { UNKNOWN, MALE, FEMALE }; /* Returns the known sex for a given first name, or UNKNOWN if unknown. */ static enum sex classify_first_name (const char *name) { const struct first_name *p = find_first_name (name); if (p) { if (p->p_boy >= 0.99f) return MALE; if (p->p_boy <= 0.01f) return FEMALE; } return UNKNOWN; } /* Returns the full name of the current user, or NULL if unknown. */ static const char * get_fullname (void) { const char *name = getlogin (); if (name != NULL) { struct passwd *pwd = getpwnam (name); if (pwd != NULL) { const char *gecos = pwd->pw_gecos; if (gecos != NULL) { /* Use the part before the first comma. See . */ const char *comma = strchr (gecos, ','); if (comma != NULL) { char *part = (char *) malloc (comma - gecos + 1); if (part != NULL) { memcpy (part, gecos, comma - gecos); part[comma - gecos] = '\0'; return part; } } else return gecos; } } } return NULL; } int main (int argc, char *argv[]) { const char *program_name = argv[0]; const char *fullname = NULL; int i; /* Parse the command-line arguments. */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (strncmp (arg, "--color=", 8) == 0) handle_color_option (arg + 8); else if (strncmp (arg, "--style=", 8) == 0) handle_style_option (arg + 8); else if (arg[0] == '-') { fprintf (stderr, "%s: invalid argument: %s\n", program_name, arg); exit (1); } else fullname = arg; } /* Handle the --color=test special argument. */ if (color_test_mode) { print_color_test (); exit (0); } if (color_mode == color_yes || (color_mode == color_tty && isatty (STDOUT_FILENO) && getenv ("NO_COLOR") == NULL) || color_mode == color_html) { /* Find the style file. */ style_file_prepare ("HELLO_STYLE", "HELLO_STYLESDIR", STYLESDIR, "hello-default.css"); /* As a fallback, use the default in the current directory. */ { struct stat statbuf; if (style_file_name == NULL || stat (style_file_name, &statbuf) < 0) style_file_name = "hello-default.css"; } } else /* No styling. */ style_file_name = NULL; /* Create a terminal output stream that uses this style file. */ styled_ostream_t stream = (color_mode == color_html ? html_styled_ostream_create (file_ostream_create (stdout), style_file_name) : styled_ostream_create (STDOUT_FILENO, "(stdout)", TTYCTL_AUTO, style_file_name)); /* Determine the full name of the user. */ if (fullname == NULL) fullname = get_fullname (); if (fullname != NULL) { ostream_write_str (stream, "Hello "); /* Associate the entire full name in CSS class 'name'. */ styled_ostream_begin_use_class (stream, "name"); const char *fullname_end = fullname + strlen (fullname); /* Determine the extent of the first name in the full name. */ const char *firstname_start; const char *firstname_end; { /* The full name can be of the form "FAMILYNAME, FIRSTNAME". */ const char *comma = strchr (fullname, ','); if (comma != NULL) { firstname_start = comma + 1; while (*firstname_start == ' ') firstname_start++; firstname_end = fullname_end; } else { /* Or it can be of the form "X. FIRSTNAME Y. FAMILYNAME". */ firstname_start = fullname; for (;;) { const char *space = strchr (firstname_start, ' '); if (space == NULL) { firstname_end = fullname_end; break; } if (space == firstname_start || space[-1] == '.') firstname_start = space + 1; else { firstname_end = space; break; } } } while (firstname_end > firstname_start && firstname_end[-1] == ' ') firstname_end--; } /* Output the part of the full name before the first name. */ ostream_write_mem (stream, fullname, firstname_start - fullname); /* Guess the sex, based on the first name. */ char *firstname = (char *) malloc (firstname_end - firstname_start + 1); memcpy (firstname, firstname_start, firstname_end - firstname_start); firstname[firstname_end - firstname_start] = '\0'; enum sex guessed_sex = classify_first_name (firstname); free (firstname); /* Associate the first name with the appropriate CSS class. */ switch (guessed_sex) { case MALE: styled_ostream_begin_use_class (stream, "boy-name"); break; case FEMALE: styled_ostream_begin_use_class (stream, "girl-name"); break; default: break; } /* Output the first name. */ ostream_write_mem (stream, firstname_start, firstname_end - firstname_start); /* Terminate the first name. */ switch (guessed_sex) { case MALE: styled_ostream_end_use_class (stream, "boy-name"); break; case FEMALE: styled_ostream_end_use_class (stream, "girl-name"); break; default: break; } /* Output the part of the full name after the first name. */ ostream_write_mem (stream, firstname_end, fullname_end - firstname_end); /* Terminate the name. */ styled_ostream_end_use_class (stream, "name"); ostream_write_str (stream, "!\n"); } else ostream_write_str (stream, "Hello!\n"); /* Flush and close the terminal stream. */ styled_ostream_free (stream); return 0; }