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