Merge Irssi 0.8.16-rc1
[silc.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 along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 (g_io_channel_write_chars(rec->handle, indent_block, CONFIG_INDENT_SIZE,
36                                              NULL, NULL) == G_IO_STATUS_ERROR)
37                         return -1;
38         }
39
40         return 0;
41 }
42
43 static int config_write_str(CONFIG_REC *rec, const char *str)
44 {
45         const char *strpos, *p;
46
47         g_return_val_if_fail(rec != NULL, -1);
48         g_return_val_if_fail(str != NULL, -1);
49
50         strpos = str;
51         while (*strpos != '\0') {
52                 /* fill the indentation */
53                 if (rec->tmp_last_lf && rec->tmp_indent_level > 0 &&
54                     *str != '\n') {
55                         if (config_write_indent(rec) == -1)
56                                 return -1;
57                 }
58
59                 p = strchr(strpos, '\n');
60                 if (p == NULL) {
61                         if (g_io_channel_write_chars(rec->handle, strpos, strlen(strpos),
62                                                      NULL, NULL) == G_IO_STATUS_ERROR)
63                                 return -1;
64                         strpos = "";
65                         rec->tmp_last_lf = FALSE;
66                 } else {
67                         if (g_io_channel_write_chars(rec->handle, strpos, (int) (p-strpos)+1,
68                                                      NULL, NULL) == G_IO_STATUS_ERROR)
69                                 return -1;
70                         strpos = p+1;
71                         rec->tmp_last_lf = TRUE;
72                 }
73         }
74
75         return 0;
76 }
77
78 static int config_has_specials(const char *text)
79 {
80         g_return_val_if_fail(text != NULL, FALSE);
81
82         while (*text != '\0') {
83                 if (!i_isalnum(*text) && *text != '_')
84                         return TRUE;
85                 text++;
86         }
87
88         return FALSE;
89 }
90
91 static char *config_escape_string(const char *text)
92 {
93         GString *str;
94         char *ret;
95
96         g_return_val_if_fail(text != NULL, NULL);
97
98         str = g_string_new("\"");
99         while (*text != '\0') {
100                 if (*text == '\\' || *text == '"')
101                         g_string_append_printf(str, "\\%c", *text);
102                 else if ((unsigned char) *text < 32)
103                         g_string_append_printf(str, "\\%03o", *text);
104                 else
105                         g_string_append_c(str, *text);
106                 text++;
107         }
108
109         g_string_append_c(str, '"');
110
111         ret = str->str;
112         g_string_free(str, FALSE);
113         return ret;
114 }
115
116 static int config_write_word(CONFIG_REC *rec, const char *word, int string)
117 {
118         char *str;
119         int ret;
120
121         g_return_val_if_fail(rec != NULL, -1);
122         g_return_val_if_fail(word != NULL, -1);
123
124         if (!string && !config_has_specials(word))
125                 return config_write_str(rec, word);
126
127         str = config_escape_string(word);
128         ret = config_write_str(rec, str);
129         g_free(str);
130
131         return ret;
132 }
133
134 static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds);
135
136 static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds)
137 {
138         g_return_val_if_fail(rec != NULL, -1);
139         g_return_val_if_fail(node != NULL, -1);
140
141         switch (node->type) {
142         case NODE_TYPE_KEY:
143                 if (config_write_word(rec, node->key, FALSE) == -1 ||
144                     config_write_str(rec, " = ") == -1 ||
145                     config_write_word(rec, node->value, TRUE) == -1)
146                         return -1;
147                 break;
148         case NODE_TYPE_VALUE:
149                 if (config_write_word(rec, node->value, TRUE) == -1)
150                         return -1;
151                 break;
152         case NODE_TYPE_BLOCK:
153                 /* key = { */
154                 if (node->key != NULL) {
155                         if (config_write_word(rec, node->key, FALSE) == -1 ||
156                             config_write_str(rec, " = ") == -1)
157                                 return -1;
158                 }
159                 if (config_write_str(rec, line_feeds ? "{\n" : "{ ") == -1)
160                         return -1;
161
162                 /* ..block.. */
163                 rec->tmp_indent_level += CONFIG_INDENT_SIZE;
164                 if (config_write_block(rec, node, FALSE, line_feeds) == -1)
165                         return -1;
166                 rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
167
168                 /* }; */
169                 if (config_write_str(rec, "}") == -1)
170                         return -1;
171                 break;
172         case NODE_TYPE_LIST:
173                 /* key = ( */
174                 if (node->key != NULL) {
175                         if (config_write_word(rec, node->key, FALSE) == -1 ||
176                             config_write_str(rec, " = ") == -1)
177                                 return -1;
178                 }
179                 if (config_write_str(rec, line_feeds ? "(\n" : "( ") == -1)
180                         return -1;
181
182                 /* ..list.. */
183                 rec->tmp_indent_level += CONFIG_INDENT_SIZE;
184                 if (config_write_block(rec, node, TRUE, line_feeds) == -1)
185                         return -1;
186                 rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
187
188                 /* ); */
189                 if (config_write_str(rec, ")") == -1)
190                         return -1;
191                 break;
192         case NODE_TYPE_COMMENT:
193                 if (node->value == NULL)
194                         break;
195
196                 if (config_write_str(rec, "#") == -1 ||
197                     config_write_str(rec, node->value) == -1)
198                         return -1;
199                 break;
200         }
201
202         return 0;
203 }
204
205 static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node);
206
207 static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
208 {
209         int len;
210
211         switch (node->type) {
212         case NODE_TYPE_KEY:
213                 /* "key = value; " */
214                 len = 5 + strlen(node->key) + strlen(node->value);
215                 break;
216         case NODE_TYPE_VALUE:
217                 /* "value, " */
218                 len = 2 + strlen(node->value);
219                 break;
220         case NODE_TYPE_BLOCK:
221         case NODE_TYPE_LIST:
222                 /* "{ list }; " */
223                 len = 6;
224                 if (node->key != NULL) len += strlen(node->key);
225                 len += config_block_get_length(rec, node);
226                 break;
227         default:
228                 /* comments always split the line */
229                 len = 1000;
230                 break;
231         }
232
233         return len;
234 }
235
236 /* return the number of characters `node' and it's subnodes take
237    if written to file */
238 static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
239 {
240         GSList *tmp;
241         int len;
242
243         len = 0;
244         for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
245                 CONFIG_NODE *subnode = tmp->data;
246
247                 len += config_node_get_length(rec, subnode);
248                 if (len > MAX_CHARS_IN_LINE) return len;
249         }
250
251         return len;
252 }
253
254 /* check if `node' and it's subnodes fit in one line in the config file */
255 static int config_block_fit_one_line(CONFIG_REC *rec, CONFIG_NODE *node)
256 {
257         g_return_val_if_fail(rec != NULL, 0);
258         g_return_val_if_fail(node != NULL, 0);
259
260         return rec->tmp_indent_level +
261                 config_node_get_length(rec, node) <= MAX_CHARS_IN_LINE;
262 }
263
264 static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds)
265 {
266         GSList *tmp;
267         int list_line_feeds, node_line_feeds;
268
269         g_return_val_if_fail(rec != NULL, -1);
270         g_return_val_if_fail(node != NULL, -1);
271         g_return_val_if_fail(is_node_list(node), -1);
272
273         list_line_feeds = !config_block_fit_one_line(rec, node);
274
275         if (!line_feeds && list_line_feeds)
276                 config_write_str(rec, "\n");
277
278         for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
279                 CONFIG_NODE *subnode = tmp->data;
280
281                 node_line_feeds = !line_feeds ? FALSE : !config_block_fit_one_line(rec, subnode);
282                 if (config_write_node(rec, subnode, node_line_feeds) == -1)
283                         return -1;
284
285                 if (subnode->type == NODE_TYPE_COMMENT)
286                         config_write_str(rec, "\n");
287                 else if (list) {
288                         if (tmp->next != NULL)
289                                 config_write_str(rec, list_line_feeds ? ",\n" : ", ");
290                         else
291                                 config_write_str(rec, list_line_feeds ? "\n" : " ");
292                 } else {
293                         config_write_str(rec, list_line_feeds ? ";\n" : "; ");
294                 }
295         }
296
297         return 0;
298 }
299
300 int config_write(CONFIG_REC *rec, const char *fname, int create_mode)
301 {
302         int ret;
303         int fd;
304
305         g_return_val_if_fail(rec != NULL, -1);
306         g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1);
307         g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1);
308
309         fd = open(fname != NULL ? fname : rec->fname,
310                            O_WRONLY | O_TRUNC | O_CREAT,
311                            create_mode != -1 ? create_mode : rec->create_mode);
312         if (fd == -1)
313                 return config_error(rec, g_strerror(errno));
314
315         rec->handle = g_io_channel_unix_new(fd);
316         g_io_channel_set_encoding(rec->handle, NULL, NULL);
317         g_io_channel_set_close_on_unref(rec->handle, TRUE);
318         rec->tmp_indent_level = 0;
319         rec->tmp_last_lf = TRUE;
320         ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
321         if (ret == -1) {
322                 /* write error */
323                 config_error(rec, errno == 0 ? "bug" : g_strerror(errno));
324         }
325
326         g_io_channel_unref(rec->handle);
327         rec->handle = NULL;
328
329         return ret;
330 }