imported irssi.
[silc.git] / apps / irssi / src / lib-config / write.c
diff --git a/apps/irssi/src/lib-config/write.c b/apps/irssi/src/lib-config/write.c
new file mode 100644 (file)
index 0000000..446735c
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ write.c : irssi configuration - write configuration file
+
+    Copyright (C) 1999 Timo Sirainen
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "module.h"
+
+/* maximum length of lines in config file before splitting them to multiple lines */
+#define MAX_CHARS_IN_LINE 70
+
+#define CONFIG_INDENT_SIZE 2
+static const char *indent_block = "  "; /* needs to be the same size as CONFIG_INDENT_SIZE! */
+
+/* write needed amount of indentation to the start of the line */
+static int config_write_indent(CONFIG_REC *rec)
+{
+       int n;
+
+       for (n = 0; n < rec->tmp_indent_level/CONFIG_INDENT_SIZE; n++) {
+               if (write(rec->handle, indent_block, CONFIG_INDENT_SIZE) == -1)
+                       return -1;
+       }
+
+       return 0;
+}
+
+static int config_write_str(CONFIG_REC *rec, const char *str)
+{
+       const char *strpos, *p;
+
+       g_return_val_if_fail(rec != NULL, -1);
+       g_return_val_if_fail(str != NULL, -1);
+
+       strpos = str;
+       while (*strpos != '\0') {
+               /* fill the indentation */
+               if (rec->tmp_last_lf && rec->tmp_indent_level > 0 &&
+                   *str != '\n') {
+                       if (config_write_indent(rec) == -1)
+                               return -1;
+               }
+
+               p = strchr(strpos, '\n');
+               if (p == NULL) {
+                       if (write(rec->handle, strpos, strlen(strpos)) == -1)
+                               return -1;
+                       strpos = "";
+                       rec->tmp_last_lf = FALSE;
+               } else {
+                       if (write(rec->handle, strpos, (int) (p-strpos)+1) == -1)
+                               return -1;
+                       strpos = p+1;
+                       rec->tmp_last_lf = TRUE;
+               }
+       }
+
+       return 0;
+}
+
+static int config_has_specials(const char *text)
+{
+       g_return_val_if_fail(text != NULL, FALSE);
+
+       while (*text != '\0') {
+               if (!isalnum((int) *text) && *text != '_')
+                       return TRUE;
+               text++;
+       }
+
+       return FALSE;
+}
+
+static int get_octal(int decimal)
+{
+       int octal, pos;
+
+       octal = 0; pos = 0;
+       while (decimal > 0) {
+               octal += (decimal & 7)*(pos == 0 ? 1 : pos);
+               decimal /= 8;
+               pos += 10;
+       }
+
+       return octal;
+}
+
+static char *config_escape_string(const char *text)
+{
+       GString *str;
+       char *ret;
+
+       g_return_val_if_fail(text != NULL, NULL);
+
+       str = g_string_new("\"");
+       while (*text != '\0') {
+               if (*text == '\\' || *text == '"')
+                       g_string_sprintfa(str, "\\%c", *text);
+               else if ((unsigned char) *text < 32)
+                       g_string_sprintfa(str, "\\%03d", get_octal(*text));
+               else
+                       g_string_append_c(str, *text);
+               text++;
+       }
+
+       g_string_append_c(str, '"');
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+       return ret;
+}
+
+static int config_write_word(CONFIG_REC *rec, const char *word, int string)
+{
+       char *str;
+       int ret;
+
+       g_return_val_if_fail(rec != NULL, -1);
+       g_return_val_if_fail(word != NULL, -1);
+
+       if (!string && !config_has_specials(word))
+               return config_write_str(rec, word);
+
+       str = config_escape_string(word);
+       ret = config_write_str(rec, str);
+       g_free(str);
+
+       return ret;
+}
+
+static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds);
+
+static int config_write_node(CONFIG_REC *rec, CONFIG_NODE *node, int line_feeds)
+{
+       g_return_val_if_fail(rec != NULL, -1);
+       g_return_val_if_fail(node != NULL, -1);
+
+       switch (node->type) {
+       case NODE_TYPE_KEY:
+               if (config_write_word(rec, node->key, FALSE) == -1 ||
+                   config_write_str(rec, " = ") == -1 ||
+                   config_write_word(rec, node->value, TRUE) == -1)
+                       return -1;
+               break;
+       case NODE_TYPE_VALUE:
+               if (config_write_word(rec, node->value, TRUE) == -1)
+                       return -1;
+               break;
+       case NODE_TYPE_BLOCK:
+               /* key = { */
+               if (node->key != NULL) {
+                       if (config_write_word(rec, node->key, FALSE) == -1 ||
+                           config_write_str(rec, " = ") == -1)
+                               return -1;
+               }
+               if (config_write_str(rec, line_feeds ? "{\n" : "{ ") == -1)
+                       return -1;
+
+               /* ..block.. */
+               rec->tmp_indent_level += CONFIG_INDENT_SIZE;
+               if (config_write_block(rec, node, FALSE, line_feeds) == -1)
+                       return -1;
+               rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
+
+               /* }; */
+               if (config_write_str(rec, "}") == -1)
+                       return -1;
+               break;
+       case NODE_TYPE_LIST:
+               /* key = ( */
+               if (node->key != NULL) {
+                       if (config_write_word(rec, node->key, FALSE) == -1 ||
+                           config_write_str(rec, " = ") == -1)
+                               return -1;
+               }
+               if (config_write_str(rec, line_feeds ? "(\n" : "( ") == -1)
+                       return -1;
+
+               /* ..list.. */
+               rec->tmp_indent_level += CONFIG_INDENT_SIZE;
+               if (config_write_block(rec, node, TRUE, line_feeds) == -1)
+                       return -1;
+               rec->tmp_indent_level -= CONFIG_INDENT_SIZE;
+
+               /* ); */
+               if (config_write_str(rec, ")") == -1)
+                       return -1;
+               break;
+       case NODE_TYPE_COMMENT:
+               if (node->value == NULL)
+                       break;
+
+               if (config_write_str(rec, "#") == -1 ||
+                   config_write_str(rec, node->value) == -1)
+                       return -1;
+               break;
+       }
+
+       return 0;
+}
+
+static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node);
+
+static int config_node_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
+{
+       int len;
+
+       switch (node->type) {
+       case NODE_TYPE_KEY:
+               /* "key = value; " */
+               len = 5 + strlen(node->key) + strlen(node->value);
+               break;
+       case NODE_TYPE_VALUE:
+               /* "value, " */
+               len = 2 + strlen(node->value);
+               break;
+       case NODE_TYPE_BLOCK:
+       case NODE_TYPE_LIST:
+               /* "{ list }; " */
+               len = 6;
+               if (node->key != NULL) len += strlen(node->key);
+               len += config_block_get_length(rec, node);
+               break;
+       default:
+                /* comments always split the line */
+               len = 1000;
+               break;
+       }
+
+       return len;
+}
+
+/* return the number of characters `node' and it's subnodes take
+   if written to file */
+static int config_block_get_length(CONFIG_REC *rec, CONFIG_NODE *node)
+{
+       GSList *tmp;
+       int len;
+
+       len = 0;
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               CONFIG_NODE *subnode = tmp->data;
+
+                len += config_node_get_length(rec, subnode);
+               if (len > MAX_CHARS_IN_LINE) return len;
+       }
+
+       return len;
+}
+
+/* check if `node' and it's subnodes fit in one line in the config file */
+static int config_block_fit_one_line(CONFIG_REC *rec, CONFIG_NODE *node)
+{
+       g_return_val_if_fail(rec != NULL, 0);
+       g_return_val_if_fail(node != NULL, 0);
+
+       return rec->tmp_indent_level +
+               config_node_get_length(rec, node) <= MAX_CHARS_IN_LINE;
+}
+
+static int config_write_block(CONFIG_REC *rec, CONFIG_NODE *node, int list, int line_feeds)
+{
+       GSList *tmp;
+       int list_line_feeds, node_line_feeds;
+
+       g_return_val_if_fail(rec != NULL, -1);
+       g_return_val_if_fail(node != NULL, -1);
+       g_return_val_if_fail(is_node_list(node), -1);
+
+       list_line_feeds = !config_block_fit_one_line(rec, node);
+
+       if (!line_feeds && list_line_feeds)
+               config_write_str(rec, "\n");
+
+       for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+               CONFIG_NODE *subnode = tmp->data;
+
+               node_line_feeds = !line_feeds ? FALSE : !config_block_fit_one_line(rec, subnode);
+               if (config_write_node(rec, subnode, node_line_feeds) == -1)
+                       return -1;
+
+               if (subnode->type == NODE_TYPE_COMMENT)
+                       config_write_str(rec, "\n");
+               else if (list) {
+                       if (tmp->next != NULL)
+                               config_write_str(rec, list_line_feeds ? ",\n" : ", ");
+                       else
+                               config_write_str(rec, list_line_feeds ? "\n" : " ");
+               } else {
+                       config_write_str(rec, list_line_feeds ? ";\n" : "; ");
+               }
+       }
+
+       return 0;
+}
+
+/* Write configuration file. Write to `fname' if it's not NULL. */
+int config_write(CONFIG_REC *rec, const char *fname, int create_mode)
+{
+       int ret;
+
+       g_return_val_if_fail(rec != NULL, -1);
+        g_return_val_if_fail(fname != NULL || rec->fname != NULL, -1);
+        g_return_val_if_fail(create_mode != -1 || rec->create_mode != -1, -1);
+
+       if (rec->handle != -1)
+               close(rec->handle);
+
+       rec->handle = open(fname != NULL ? fname : rec->fname,
+                          O_WRONLY | O_TRUNC | O_CREAT,
+                          create_mode != -1 ? create_mode : rec->create_mode);
+       if (rec->handle == -1)
+               return config_error(rec, g_strerror(errno));
+
+       rec->tmp_indent_level = 0;
+       rec->tmp_last_lf = TRUE;
+        ret = config_write_block(rec, rec->mainnode, FALSE, TRUE);
+       if (ret == -1) {
+               /* write error */
+               config_error(rec, errno == 0 ? "bug" : g_strerror(errno));
+       }
+
+       close(rec->handle);
+       rec->handle = -1;
+
+       return ret;
+}