Added SILC Thread Queue API
[runtime.git] / apps / irssi / src / lib-config / write.c
1 /*
2  write.c : irssi configuration - write configuration file
3
4     Copyright (C) 1999 Timo Sirainen
5
6     This program 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 2 of the License, or
9     (at your option) any later version.
10
11     This program 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, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22
23 /* maximum length of lines in config file before splitting them to multiple lines */
24 #define MAX_CHARS_IN_LINE 70
25
26 #define CONFIG_INDENT_SIZE 2
27 static const char *indent_block = "  "; /* needs to be the same size as CONFIG_INDENT_SIZE! */
28
29 /* write needed amount of indentation to the start of the line */
30 static int config_write_indent(CONFIG_REC *rec)
31 {
32         int n;
33
34         for (n = 0; n < rec->tmp_indent_level/CONFIG_INDENT_SIZE; n++) {
35                 if (write(rec->handle, indent_block, CONFIG_INDENT_SIZE) == -1)
36                         return -1;
37         }
38
39         return 0;
40 }
41
42 static int config_write_str(CONFIG_REC *rec, const char *str)
43 {
44         const char *strpos, *p;
45
46         g_return_val_if_fail(rec != NULL, -1);
47         g_return_val_if_fail(str != NULL, -1);
48
49         strpos = str;
50         while (*strpos != '\0') {
51                 /* fill the indentation */
52                 if (rec->tmp_last_lf && rec->tmp_indent_level > 0 &&
53                     *str != '\n') {
54                         if (config_write_indent(rec) == -1)
55                                 return -1;
56                 }
57
58                 p = strchr(strpos, '\n');
59                 if (p == NULL) {
60                         if (write(rec->handle, strpos, strlen(strpos)) == -1)
61                                 return -1;
62                         strpos = "";
63                         rec->tmp_last_lf = FALSE;
64                 } else {
65                         if (write(rec->handle, strpos, (int) (p-strpos)+1) == -1)
66                                 return -1;
67                         strpos = p+1;
68                         rec->tmp_last_lf = TRUE;
69                 }
70         }
71
72         return 0;
73 }
74
75 static int config_has_specials(const char *text)
76 {
77         g_return_val_if_fail(text != NULL, FALSE);
78
79         while (*text != '\0') {
80                 if (!i_isalnum(*text) && *text != '_')
81                         return TRUE;
82                 text++;
83         }
84
85         return FALSE;
86 }
87
88 static int get_octal(int decimal)
89 {
90         int octal, pos;
91
92         octal = 0; pos = 0;
93         while (decimal > 0) {
94                 octal += (decimal & 7)*(pos == 0 ? 1 : pos);
95                 decimal /= 8;
96                 pos += 10;
97         }
98
99         return octal;
100 }
101
102 static char *config_escape_string(const char *text)
103 {
104         GString *str;
105         char *ret;
106
107         g_return_val_if_fail(text != NULL, NULL);
108
109         str = g_string_new("\"");
110         while (*text != '\0') {
111                 if (*text == '\\' || *text == '"')
112                         g_string_sprintfa(str, "\\%c", *text);
113                 else if ((unsigned char) *text < 32)
114                         g_string_sprintfa(str, "\\%03d", get_octal(*text));
115                 else
116                         g_string_append_c(str, *text);
117                 text++;
118         }
119
120         g_string_append_c(str, '"');
121
122         ret = str->str;
123         g_string_free(str, FALSE);
124         return ret;
125 }
126
127 static int config_write_word(CONFIG_REC *rec, const char *word, int string)
128 {
129         char *str;
130         int ret;
131
132         g_return_val_if_fail(rec != NULL, -1);
133         g_return_val_if_fail(word != NULL, -1);
134
135         if (!string && !config_has_specials(word))
136                 return config_write_str(rec, word);
137
138         str = config_escape_string(word);
139         ret = config_write_str(rec, str);
140         g_free(str);
141
142         return ret;
143 }
144
145 static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds);
146
147 static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds)
148 {
149         g_return_val_if_fail(rec != NULL, -1);
150         g_return_val_if_fail(node != NULL, -1);
151
152         switch (node->type) {
153         case NODE_TYPE_KEY:
154                 if (config_write_word(rec, node->key, FALSE) == -1 ||
155                     config_write_str(rec, " = ") == -1 ||
156                     config_write_word(rec, node->value, TRUE) == -1)
157                         return -1;
158                 break;
159         case NODE_TYPE_VALUE:
160                 if (config_write_word(rec, node->value, TRUE) == -1)
161                         return -1;
162                 break;
163         case NODE_TYPE_BLOCK:
164                 /* key = { */
165                 if (node->key != NULL) {
166                         if (config_write_word(rec, node->key, FALSE) == -1 ||
167                             config_write_str(rec, " = ") == -1)
168                                 return -1;
169                 }
170                 if (config_write_str(rec, line_feeds ? "{\n" : "{ ") == -1)
171                         return -1;
172
173                 /* ..block.. */
174                 rec->tmp_indent_level += CONFIG_INDENT_SIZE;
175                 if (config_write_block(rec, node, FALSE, line_feeds) == -1)
176                         return -1;
177                 rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
178
179                 /* }; */
180                 if (config_write_str(rec, "}") == -1)
181                         return -1;
182                 break;
183         case NODE_TYPE_LIST:
184                 /* key = ( */
185                 if (node->key != NULL) {
186                         if (config_write_word(rec, node->key, FALSE) == -1 ||
187                             config_write_str(rec, " = ") == -1)
188                                 return -1;
189                 }
190                 if (config_write_str(rec, line_feeds ? "(\n" : "( ") == -1)
191                         return -1;
192
193                 /* ..list.. */
194                 rec->tmp_indent_level += CONFIG_INDENT_SIZE;
195                 if (config_write_block(rec, node, TRUE, line_feeds) == -1)
196                         return -1;
197                 rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
198
199                 /* ); */
200                 if (config_write_str(rec, ")") == -1)
201                         return -1;
202                 break;
203         case NODE_TYPE_COMMENT:
204                 if (node->value == NULL)
205                         break;
206
207                 if (config_write_str(rec, "#") == -1 ||
208                     config_write_str(rec, node->value) == -1)
209                         return -1;
210                 break;
211         }
212
213         return 0;
214 }
215
216 static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node);
217
218 static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
219 {
220         int len;
221
222         switch (node->type) {
223         case NODE_TYPE_KEY:
224                 /* "key = value; " */
225                 len = 5 + strlen(node->key) + strlen(node->value);
226                 break;
227         case NODE_TYPE_VALUE:
228                 /* "value, " */
229                 len = 2 + strlen(node->value);
230                 break;
231         case NODE_TYPE_BLOCK:
232         case NODE_TYPE_LIST:
233                 /* "{ list }; " */
234                 len = 6;
235                 if (node->key != NULL) len += strlen(node->key);
236                 len += config_block_get_length(rec, node);
237                 break;
238         default:
239                 /* comments always split the line */
240                 len = 1000;
241                 break;
242         }
243
244         return len;
245 }
246
247 /* return the number of characters `node' and it's subnodes take
248    if written to file */
249 static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
250 {
251         GSList *tmp;
252         int len;
253
254         len = 0;
255         for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
256                 CONFIG_NODE *subnode = tmp->data;
257
258                 len += config_node_get_length(rec, subnode);
259                 if (len > MAX_CHARS_IN_LINE) return len;
260         }
261
262         return len;
263 }
264
265 /* check if `node' and it's subnodes fit in one line in the config file */
266 static int config_block_fit_one_line(CONFIG_REC *rec, CONFIG_NODE *node)
267 {
268         g_return_val_if_fail(rec != NULL, 0);
269         g_return_val_if_fail(node != NULL, 0);
270
271         return rec->tmp_indent_level +
272                 config_node_get_length(rec, node) <= MAX_CHARS_IN_LINE;
273 }
274
275 static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds)
276 {
277         GSList *tmp;
278         int list_line_feeds, node_line_feeds;
279
280         g_return_val_if_fail(rec != NULL, -1);
281         g_return_val_if_fail(node != NULL, -1);
282         g_return_val_if_fail(is_node_list(node), -1);
283
284         list_line_feeds = !config_block_fit_one_line(rec, node);
285
286         if (!line_feeds && list_line_feeds)
287                 config_write_str(rec, "\n");
288
289         for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
290                 CONFIG_NODE *subnode = tmp->data;
291
292                 node_line_feeds = !line_feeds ? FALSE : !config_block_fit_one_line(rec, subnode);
293                 if (config_write_node(rec, subnode, node_line_feeds) == -1)
294                         return -1;
295
296                 if (subnode->type == NODE_TYPE_COMMENT)
297                         config_write_str(rec, "\n");
298                 else if (list) {
299                         if (tmp->next != NULL)
300                                 config_write_str(rec, list_line_feeds ? ",\n" : ", ");
301                         else
302                                 config_write_str(rec, list_line_feeds ? "\n" : " ");
303                 } else {
304                         config_write_str(rec, list_line_feeds ? ";\n" : "; ");
305                 }
306         }
307
308         return 0;
309 }
310
311 /* Write configuration file. Write to `fname' if it's not NULL. */
312 int config_write(CONFIG_REC *rec, const char *fname, int create_mode)
313 {
314         int ret;
315
316         g_return_val_if_fail(rec != NULL, -1);
317         g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1);
318         g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1);
319
320         if (rec->handle != -1)
321                 close(rec->handle);
322
323         rec->handle = open(fname != NULL ? fname : rec->fname,
324                            O_WRONLY | O_TRUNC | O_CREAT,
325                            create_mode != -1 ? create_mode : rec->create_mode);
326         if (rec->handle == -1)
327                 return config_error(rec, g_strerror(errno));
328
329         rec->tmp_indent_level = 0;
330         rec->tmp_last_lf = TRUE;
331         ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
332         if (ret == -1) {
333                 /* write error */
334                 config_error(rec, errno == 0 ? "bug" : g_strerror(errno));
335         }
336
337         close(rec->handle);
338         rec->handle = -1;
339
340         return ret;
341 }