Re-indent code and use test macros for betetr readability
[gnupg.git] / common / helpfile.c
1 /* helpfile.c - GnuPG's helpfile feature
2  *      Copyright (C) 2007 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdlib.h>
22
23
24 #include "util.h"
25 #include "i18n.h"
26 #include "membuf.h"
27
28
29 /* Try to find KEY in the file FNAME.  */
30 static char *
31 findkey_fname (const char *key, const char *fname)
32 {
33   gpg_error_t err = 0;
34   FILE *fp;
35   int lnr = 0;
36   int c;
37   char *p, line[256];
38   int in_item = 0;
39   membuf_t mb = MEMBUF_ZERO;
40
41   fp = fopen (fname, "r");
42   if (!fp)
43     {
44       if (errno != ENOENT)
45         {
46           err = gpg_error_from_syserror ();
47           log_error (_("can't open `%s': %s\n"), fname, gpg_strerror (err));
48         }
49       return NULL;
50     }
51
52   while (fgets (line, DIM(line)-1, fp))
53     {
54       lnr++;
55       
56       if (!*line || line[strlen(line)-1] != '\n')
57         {
58           /* Eat until end of line. */
59           while ( (c=getc (fp)) != EOF && c != '\n')
60             ;
61           err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
62                            : GPG_ERR_INCOMPLETE_LINE);
63           log_error (_("file `%s', line %d: %s\n"),
64                      fname, lnr, gpg_strerror (err));
65         }
66       else
67         line[strlen(line)-1] = 0; /* Chop the LF. */
68       
69     again:
70       if (!in_item)
71         {
72           /* Allow for empty lines and spaces while not in an item. */
73           for (p=line; spacep (p); p++)
74             ;
75           if (!*p || *p == '#')
76             continue;
77           if (*line != '.' || spacep(line+1))
78             {
79               log_info (_("file `%s', line %d: %s\n"),
80                         fname, lnr, _("ignoring garbage line"));
81               continue;
82             }
83           trim_trailing_spaces (line);
84           in_item = 1;
85           if (!strcmp (line+1, key))
86             {
87               /* Found.  Start collecting.  */
88               init_membuf (&mb, 1024);
89             }
90           continue;
91         }
92
93       /* If in an item only allow for comments in the first column
94          and provide ". " as an escape sequence to allow for
95          leading dots and hash marks in the actual text.  */
96       if (*line == '#')
97         continue;
98       if (*line == '.')
99         { 
100           if (spacep(line+1))
101             p = line + 2;
102           else
103             {
104               trim_trailing_spaces (line);
105               in_item = 0;
106               if (is_membuf_ready (&mb))
107                 break;        /* Yep, found and collected the item.  */
108               if (!line[1])
109                 continue;     /* Just an end of text dot. */
110               goto again;     /* A new key line.  */
111             }
112         }
113       else
114         p = line;
115
116       if (is_membuf_ready (&mb))
117         {
118           put_membuf_str (&mb, p);
119           put_membuf (&mb, "\n", 1);
120         }
121
122     }
123   if ( !err && ferror (fp) )
124     {
125       err = gpg_error_from_syserror ();
126       log_error (_("error reading `%s', line %d: %s\n"),
127                  fname, lnr, gpg_strerror (err));
128     }
129   
130   fclose (fp);
131   if (is_membuf_ready (&mb))
132     {
133       /* We have collected something.  */
134       if (err)
135         {
136           xfree (get_membuf (&mb, NULL));
137           return NULL;
138         }
139       else
140         {
141           put_membuf (&mb, "", 1);  /* Terminate string.  */
142           return get_membuf (&mb, NULL);
143         }
144     }
145   else
146     return NULL;
147 }
148
149
150 /* Try the help files depending on the locale.  */
151 static char *
152 findkey_locale (const char *key, const char *locname,
153                 int only_current_locale, const char *dirname)
154 {
155   const char *s;
156   char *fname, *ext, *p;
157   char *result;
158
159   fname = xtrymalloc (strlen (dirname) + 6 + strlen (locname) + 4 + 1);
160   if (!fname)
161     return NULL;
162   ext = stpcpy (stpcpy (fname, dirname), "/help.");
163   /* Search with locale name and territory.  ("help.LL_TT.txt") */
164   if (strchr (locname, '_'))
165     {
166       strcpy (stpcpy (ext, locname), ".txt");
167       result = findkey_fname (key, fname);
168     }
169   else
170     result = NULL;  /* No territory.  */
171
172   if (!result)
173     {
174       /* Search with just the locale name - if any. ("help.LL.txt") */
175       if (*locname)
176         {
177           for (p=ext, s=locname; *s && *s != '_';)
178             *p++ = *s++;
179           strcpy (p, ".txt");
180           result = findkey_fname (key, fname);
181         }
182       else
183         result = NULL;
184     }
185   
186   if (!result && (!only_current_locale || !*locname) )
187     {
188       /* Last try: Search in file without any locale info.  ("help.txt") */
189       strcpy (ext, "txt");
190       result = findkey_fname (key, fname);
191     }
192
193   xfree (fname);
194   return result;
195 }
196
197
198 /* Return a malloced help text as identified by KEY.  The system takes
199    the string from an UTF-8 encoded file to be created by an
200    administrator or as distributed with GnuPG.  On a GNU or Unix
201    system the entry is searched in these files:
202
203      /etc/gnupg/help.LL.txt
204      /etc/gnupg/help.txt
205      /usr/share/gnupg/help.LL.txt
206      /usr/share/gnupg/help.txt
207      
208    Here LL denotes the two digit language code of the current locale.
209    If ONLY_CURRENT_LOCALE is set, the fucntion won;t fallback to the
210    english valiant ("help.txt") unless that locale has been requested.
211    
212    The help file needs to be encoded in UTF-8, lines with a '#' in the
213    first column are comment lines and entirely ignored.  Help keys are
214    identified by a key consisting of a single word with a single dot
215    as the first character.  All key lines listed without any
216    intervening lines (except for comment lines) lead to the same help
217    text.  Lines following the key lines make up the actual hep texts.
218    
219 */
220
221 char *
222 gnupg_get_help_string (const char *key, int only_current_locale)
223 {
224   static const char *locname;
225   char *result;
226
227   if (!locname)
228     {
229       char *buffer, *p;
230       int count = 0;
231       const char *s = gnupg_messages_locale_name ();
232       buffer = xtrystrdup (s);
233       if (!buffer)
234         locname = "";
235       else
236         {
237           for (p = buffer; *p; p++)
238             if (*p == '.' || *p == '@' || *p == '/' /*(safeguard)*/)
239               *p = 0;
240             else if (*p == '_')
241               {
242                 if (count++)
243                   *p = 0;  /* Also cut at a underscore in the territory.  */
244               }
245           locname = buffer;
246         }
247     }
248
249   if (!key || !*key)
250     return NULL;
251
252   result = findkey_locale (key, locname, only_current_locale, 
253                            gnupg_sysconfdir ());
254   if (!result)
255     result = findkey_locale (key, locname, only_current_locale,
256                              gnupg_datadir ());
257
258   if (result)
259     trim_trailing_spaces (result);
260
261   return result;
262 }