blob: 272053d74b2b815586753b45ff5157405107f10b [file] [log] [blame]
/* Example program for GNU libtextstyle.
Copyright (C) 2018-2019 Free Software Foundation, Inc.
Written by Bruno Haible <bruno@clisp.org>, 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 <https://www.gnu.org/licenses/>. */
/* Source code of the C program. */
#include <textstyle.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#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 <https://en.wikipedia.org/wiki/Gecos_field>. */
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;
}