• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Example program for GNU libtextstyle.
2    Copyright (C) 2018-2019 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2018.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 /* Source code of the C program.  */
19 
20 #include <textstyle.h>
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <pwd.h>
27 #include <sys/stat.h>
28 
29 #include "names.c"
30 
31 /* Returns the record for a given first name, or NULL if not found.  */
32 static const struct first_name *
find_first_name(const char * name)33 find_first_name (const char *name)
34 {
35   size_t i = 0;
36   size_t j = sizeof (names) / sizeof (names[0]);
37   /* Loop invariants:
38      1. j > i,
39      2. If name is in the table, then at an index >= i, < j.  */
40   while (j - i > 1)
41     {
42       size_t k = i + (j - i) / 2;
43       if (strcmp (names[k].name, name) <= 0)
44         i = k;
45       else
46         j = k;
47     }
48   /* Here j = i + 1.  */
49   if (strcmp (names[i].name, name) == 0)
50     return &names[i];
51   else
52     return NULL;
53 }
54 
55 enum sex
56 {
57   UNKNOWN,
58   MALE,
59   FEMALE
60 };
61 
62 /* Returns the known sex for a given first name, or UNKNOWN if unknown.  */
63 static enum sex
classify_first_name(const char * name)64 classify_first_name (const char *name)
65 {
66   const struct first_name *p = find_first_name (name);
67   if (p)
68     {
69       if (p->p_boy >= 0.99f)
70         return MALE;
71       if (p->p_boy <= 0.01f)
72         return FEMALE;
73     }
74   return UNKNOWN;
75 }
76 
77 /* Returns the full name of the current user, or NULL if unknown.  */
78 static const char *
get_fullname(void)79 get_fullname (void)
80 {
81   const char *name = getlogin ();
82   if (name != NULL)
83     {
84       struct passwd *pwd = getpwnam (name);
85       if (pwd != NULL)
86         {
87           const char *gecos = pwd->pw_gecos;
88           if (gecos != NULL)
89             {
90               /* Use the part before the first comma.
91                  See <https://en.wikipedia.org/wiki/Gecos_field>.  */
92               const char *comma = strchr (gecos, ',');
93               if (comma != NULL)
94                 {
95                   char *part = (char *) malloc (comma - gecos + 1);
96                   if (part != NULL)
97                     {
98                       memcpy (part, gecos, comma - gecos);
99                       part[comma - gecos] = '\0';
100                       return part;
101                     }
102                 }
103               else
104                 return gecos;
105             }
106         }
107     }
108   return NULL;
109 }
110 
111 int
main(int argc,char * argv[])112 main (int argc, char *argv[])
113 {
114   const char *program_name = argv[0];
115   const char *fullname = NULL;
116   int i;
117 
118   /* Parse the command-line arguments.  */
119   for (i = 1; i < argc; i++)
120     {
121       const char *arg = argv[i];
122       if (strncmp (arg, "--color=", 8) == 0)
123         handle_color_option (arg + 8);
124       else if (strncmp (arg, "--style=", 8) == 0)
125         handle_style_option (arg + 8);
126       else if (arg[0] == '-')
127         {
128           fprintf (stderr, "%s: invalid argument: %s\n", program_name, arg);
129           exit (1);
130         }
131       else
132         fullname = arg;
133     }
134 
135   /* Handle the --color=test special argument.  */
136   if (color_test_mode)
137     {
138       print_color_test ();
139       exit (0);
140     }
141 
142   if (color_mode == color_yes
143       || (color_mode == color_tty
144           && isatty (STDOUT_FILENO)
145           && getenv ("NO_COLOR") == NULL)
146       || color_mode == color_html)
147     {
148       /* Find the style file.  */
149       style_file_prepare ("HELLO_STYLE", "HELLO_STYLESDIR", STYLESDIR,
150                           "hello-default.css");
151       /* As a fallback, use the default in the current directory.  */
152       {
153         struct stat statbuf;
154 
155         if (style_file_name == NULL || stat (style_file_name, &statbuf) < 0)
156           style_file_name = "hello-default.css";
157       }
158     }
159   else
160     /* No styling.  */
161     style_file_name = NULL;
162 
163   /* Create a terminal output stream that uses this style file.  */
164   styled_ostream_t stream =
165     (color_mode == color_html
166      ? html_styled_ostream_create (file_ostream_create (stdout),
167                                    style_file_name)
168      : styled_ostream_create (STDOUT_FILENO, "(stdout)", TTYCTL_AUTO,
169                               style_file_name));
170 
171   /* Determine the full name of the user.  */
172   if (fullname == NULL)
173     fullname = get_fullname ();
174   if (fullname != NULL)
175     {
176       ostream_write_str (stream, "Hello ");
177 
178       /* Associate the entire full name in CSS class 'name'.  */
179       styled_ostream_begin_use_class (stream, "name");
180 
181       const char *fullname_end = fullname + strlen (fullname);
182 
183       /* Determine the extent of the first name in the full name.  */
184       const char *firstname_start;
185       const char *firstname_end;
186       {
187         /* The full name can be of the form "FAMILYNAME, FIRSTNAME".  */
188         const char *comma = strchr (fullname, ',');
189         if (comma != NULL)
190           {
191             firstname_start = comma + 1;
192             while (*firstname_start == ' ')
193               firstname_start++;
194             firstname_end = fullname_end;
195           }
196         else
197           {
198             /* Or it can be of the form "X. FIRSTNAME Y. FAMILYNAME".  */
199             firstname_start = fullname;
200             for (;;)
201               {
202                 const char *space = strchr (firstname_start, ' ');
203                 if (space == NULL)
204                   {
205                     firstname_end = fullname_end;
206                     break;
207                   }
208                 if (space == firstname_start || space[-1] == '.')
209                   firstname_start = space + 1;
210                 else
211                   {
212                     firstname_end = space;
213                     break;
214                   }
215               }
216           }
217         while (firstname_end > firstname_start && firstname_end[-1] == ' ')
218           firstname_end--;
219       }
220 
221       /* Output the part of the full name before the first name.  */
222       ostream_write_mem (stream, fullname, firstname_start - fullname);
223 
224       /* Guess the sex, based on the first name.  */
225       char *firstname = (char *) malloc (firstname_end - firstname_start + 1);
226       memcpy (firstname, firstname_start, firstname_end - firstname_start);
227       firstname[firstname_end - firstname_start] = '\0';
228       enum sex guessed_sex = classify_first_name (firstname);
229       free (firstname);
230 
231       /* Associate the first name with the appropriate CSS class.  */
232       switch (guessed_sex)
233         {
234         case MALE:
235           styled_ostream_begin_use_class (stream, "boy-name");
236           break;
237         case FEMALE:
238           styled_ostream_begin_use_class (stream, "girl-name");
239           break;
240         default:
241           break;
242         }
243 
244       /* Output the first name.  */
245       ostream_write_mem (stream, firstname_start,
246                          firstname_end - firstname_start);
247 
248       /* Terminate the first name.  */
249       switch (guessed_sex)
250         {
251         case MALE:
252           styled_ostream_end_use_class (stream, "boy-name");
253           break;
254         case FEMALE:
255           styled_ostream_end_use_class (stream, "girl-name");
256           break;
257         default:
258           break;
259         }
260 
261       /* Output the part of the full name after the first name.  */
262       ostream_write_mem (stream, firstname_end, fullname_end - firstname_end);
263 
264       /* Terminate the name.  */
265       styled_ostream_end_use_class (stream, "name");
266 
267       ostream_write_str (stream, "!\n");
268     }
269   else
270     ostream_write_str (stream, "Hello!\n");
271 
272   /* Flush and close the terminal stream.  */
273   styled_ostream_free (stream);
274 
275   return 0;
276 }
277