summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS45
-rw-r--r--README1
-rw-r--r--TODO38
-rw-r--r--config138
-rw-r--r--configure.in27
-rw-r--r--docs/COMMANDS22
-rw-r--r--docs/FORMATS9
-rw-r--r--docs/PERL3
-rw-r--r--docs/SIGNALS2
-rw-r--r--docs/SPECIAL_VARS104
-rw-r--r--irssi.spec.in54
-rw-r--r--src/Makefile.am2
-rw-r--r--src/common-setup.h60
-rw-r--r--src/common.h10
-rw-r--r--src/lib-config/Makefile.am12
-rw-r--r--src/lib-config/get.c256
-rw-r--r--src/lib-config/iconfig.h136
-rw-r--r--src/lib-config/irssi-config.c189
-rw-r--r--src/lib-config/irssi-config.h25
-rw-r--r--src/lib-config/module.h6
-rw-r--r--src/lib-config/parse.c335
-rw-r--r--src/lib-config/set.c122
-rw-r--r--src/lib-config/write.c336
23 files changed, 1532 insertions, 400 deletions
diff --git a/NEWS b/NEWS
index 496f7c9b..ac57843a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,48 @@
+v0.7.90 2000-04-xx Timo Sirainen <tss@iki.fi>
+
+ * On the way to 0.8.0 .. Major rewriting/rearranging code. There's
+ some changes in behaviour because I'm trying to make Irssi a bit
+ more compatible with EPIC.
+
+ * libPropList isn't needed anymore - I'm using my own configuration
+ library. This is mostly because different proplists worked a bit
+ differently everywhere and several people had problems with it.
+ It's also yet another extra library that you needed to compile
+ Irssi. New configuration library has several advantages:
+
+ You can add comments to configuration file and they also stay
+ there when it's saved.
+
+ It's not nearly as vulnerable as proplist. If some error occurs,
+ instead of just not reading anything it will try to continue if
+ possible. Also the error messages are written to irssi's text
+ window instead of stdout.
+
+ It can be managed more easily than proplist - setting/getting the
+ configuration is a lot more easier.
+
+ * Coding style changes - I'm not using gint, gchar etc. anymore,
+ they're just extra pain when moving code to non-glib projects and
+ syntax hilighting doesn't work by default with most editors ;)
+
+ Indentation style was also changed to K&R because of some political
+ reasons ;) And I'm already starting to like it.. :) It forces me
+ to split code to different functions more often and the result is
+ that the code gets more readable.
+
+ And finally I'm also using `const' all over the place.
+
+ + /EVAL <commands> - Expand all the special variables from string and
+ run it. Commands can be split with ; character. See
+ docs/SPECIAL_VARS for more info.
+ + Aliases are parsed just like /EVAL - arguments are in $0..$9.
+ + Text formats are also parsed like /EVAL, arguments used to be in
+ $1..$9, now they're in $0..$8 so it messes up existing themes..
+ + /SET [key [value]] - no more the '=' character. Boolean values
+ also need to be changed with ON/OFF/TOGGLE values (not yes/no).
+ Settings aren't saved to disk until you use /SAVE.
+ + /TOGGLE <key> [ON/OFF] - same as /SET <key> TOGGLE
+
v0.7.28 2000-03-11 Timo Sirainen <tss@iki.fi>
+ irssi-text: New improved "text widget". It takes less memory and
diff --git a/README b/README
index d93420d7..e38b428f 100644
--- a/README
+++ b/README
@@ -68,7 +68,6 @@ make install
Configure can use these parameters (all of these defaults to yes):
- --with-proplist=dir Specify libPropList directory
--with-servertest Build test irc server which you can use to try crash
irc clients
--with-socks Build with socks library
diff --git a/TODO b/TODO
index d0966887..5036694c 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,41 @@
+ - notifylist ei toimi, /ALIAS, /IGNORE
+- g_strndup() !!!!! auttaa varmaan vaikka missä
+- server-specific source_host
+- curses sijainti jotain rikkoo
+Day changed to 30-26 2000
+[00:10] .. [00:20]
+
+ - "away mode changed"
+ - dcc on särki
+ - /server +blah tekis uuden ikkunan.
+
+[17:37] -!- Magi [^magi@magi.yok.utu.fi] has quit IRC [Killed (Uni-Stuttgart.DE ((^magi@magi.yok.utu.fi)GMD.DE <- (.@vipek-IV.vip.net.pl)*.pl[ircd@hub.irc.pl]))]
+:Magi!^magi@magi.yok.utu.fi QUIT :
+
+Kalled (gart.DE ((^magi@magi.yok.utu.fi)GMD.DE <- (.@vipek-IV.vip.net.pl)*.pl[ircd@hub.irc.pl]))
+ ~cras@0:0:0:0:0:ffff:
+ -teemoihin tee jotain pientä selitystä edes!
+ - alt+left/right vaihtaa kanavaa..
+
+ - checkkaa miten ne autojoin_channelsit nyt meni.. että vapautetaan ym.
+ - raiseta ikkuna jossa on tekstiä mut ei over aktiivisen päälle
+ - line-split.c: varmista että se 64k limitti toimii eikä esim. kaada!
+- vaihda /set nimet järkevimmiksi
+- optio että vaihtaa automaagisesti sinne autocreatettuun ikkunaan. tyhjennä
+entry (laita historyyn) ettei uusi rivi mene query ikkunaan suoraan vahingossa.
+- "älä näytä n. sekunttia pienempää lagia"
+- cmd line switchi source hostille
+- autojoinikaan ei tunnu oikein pelaavan..? vain kun vaihtuu serveri
+- quit näytettäisiin vaan yhdessä ikkunassa.
+- /exec
+ - optionaalisesti voisi niitä logeja ajella siinä toisessa irssisessiossa
+ - msg:issä kun tulee sitä away viestiä näyttäisi vaan kerran..
+ - logrotate
+ - ignoroida tietyt ctcpt. regexpit. ignorettaa tekstiä ..
+ egopallo/#tv.fi CANAL|MAFIA|...
+ - autorun.ircnet
+ - flood protectionia paremmaksi
+ - bottipluginiin tms. .. channel not available tms. rejoini
*** Bugs
diff --git a/config b/config
index e159948c..49c68c37 100644
--- a/config
+++ b/config
@@ -1,72 +1,70 @@
-{
- setupservers = (
- {server = irc.funet.fi;ircnet = IRCNet;port = 6667;autoconnect = No;},
- {server = irc.efnet.net;ircnet = EFNet;port = 6667;autoconnect = No;},
- {server = irc.undernet.net;ircnet = Undernet;port = 6667;autoconnect = No;},
- {server = irc.dal.net;ircnet = DALNet;port = 6667;autoconnect = No;},
- {server = irc.openprojects.net;ircnet = OPN;port = 6667;autoconnect = No;},
- {server = irc.ptlink.net;ircnet = PTlink;port = 6667;autoconnect = No;},
- {server = irc.multichat.org;ircnet = Multichat;port = 6667;autoconnect = No;}
- );
+setupservers = (
+ {server = "irc.funet.fi"; ircnet = IRCNet; port = 6667; autoconnect = No;},
+ {server = "irc.efnet.net"; ircnet = EFNet; port = 6667; autoconnect = No;},
+ {server = "irc.undernet.net"; ircnet = Undernet; port = 6667; autoconnect = No;},
+ {server = "irc.dal.net"; ircnet = DALNet; port = 6667; autoconnect = No;},
+ {server = "irc.openprojects.net"; ircnet = OPN; port = 6667; autoconnect = No;},
+ {server = "irc.ptlink.net"; ircnet = PTlink; port = 6667; autoconnect = No;},
+ {server = "irc.multichat.org"; ircnet = Multichat; port = 6667; autoconnect = No;}
+);
- ircnets = (
- {name = IRCNet;max_kicks = 4;max_modes = 3;max_msgs = 3;},
- {name = EFNet;max_kicks = 4;max_modes = 4;max_msgs = 3;},
- {name = Undernet;max_kicks = 4;max_modes = 3;max_msgs = 3;},
- {name = DALNet;max_kicks = 4;max_modes = 6;max_msgs = 3;},
- {name = OPN;max_kicks = 1;max_modes = 6;max_msgs = 100;},
- {name = PTlink;max_kicks = 1;max_modes = 6;max_msgs = 100;},
- {name = Multichat;max_kicks = 1;max_modes = 6;max_msgs = 100;}
- );
+ircnets = (
+ {name = IRCNet; max_kicks = 4; max_modes = 3; max_msgs = 5;},
+ {name = EFNet; max_kicks = 4; max_modes = 4; max_msgs = 3;},
+ {name = Undernet; max_kicks = 4; max_modes = 3; max_msgs = 3;},
+ {name = DALNet; max_kicks = 4; max_modes = 6; max_msgs = 3;},
+ {name = OPN; max_kicks = 1; max_modes = 6; max_msgs = 100;},
+ {name = PTlink; max_kicks = 1; max_modes = 6; max_msgs = 100;},
+ {name = Multichat; max_kicks = 1; max_modes = 6; max_msgs = 100;}
+);
- channels = (
- {
- name = "#irssi";
- ircnet = ircnet;
- autojoin = No;
- }
- );
- aliases = (
- {alias = J;command = "/join &1";},
- {alias = LEAVE;command = "/part &1";},
- {alias = BYE;command = "/quit &1";},
- {alias = WI;command = "/whois &1";},
- {alias = WII;command = "/whois %1 %1";},
- {alias = WW;command = "/whowas &1";},
- {alias = W;command = "/who *";},
- {alias = N;command = "/names *";},
- {alias = M;command = "/msg &1";},
- {alias = T;command = "/topic &1";},
- {alias = C;command = "/clear";},
- {alias = CL;command = "/clear";},
- {alias = K;command = "/kick &1";},
- {alias = KB;command = "/kickban &1";},
- {alias = KN;command = "/knockout &1";},
- {alias = B;command = "/ban &1";},
- {alias = UB;command = "/unban &1";},
- {alias = IG;command = "/ignore &1";},
- {alias = UNIG;command = "/unignore &1";},
- {alias = SB;command = "/scrollback &1";},
- {alias = UMODE;command = "/mode %n &1";}
- );
- popups = (
- {label = "<MULTICOMMA>Whois";command = "/whois %s";},
- {label = "DCC Send File";command = "/dcc send %s";},
- {label = "Open DCC Chat";command = "/dcc chat %s";},
- {label = Query;command = "/query %s";},
- {label = "<MENU><OP>";command = "Op";},
- {label = "<MULTI>Op";command = "/op %s";},
- {label = "<MULTI>Deop";command = "/deop %s";},
- {label = "<MULTI>Voice";command = "/voice %s";},
- {label = "<MULTI>Devoice";command = "/devoice %s";},
- {label = "<KICK>Kick";command = "/kick %s %s";},
- {label = "<MULTI>Ban";command = "/ban %s";},
- {label = "<KICK>Kick+ban";command = "/kickban %s %s";},
- {label = "<KICK>Knockout";command = "/knockout %s %s";},
- {label = "</MENU>";command = "";},
- {label = "<MENU>";command = "CTCP";},
- {label = Ping;command = "/ping %s";},
- {label = Version;command = "/ver %s";},
- {label = "</MENU>";command = "";}
- );
-}
+channels = (
+ {
+ name = "#irssi";
+ ircnet = ircnet;
+ autojoin = No;
+ }
+);
+aliases = (
+ {alias = J; command = "join";},
+ {alias = LEAVE; command = "part";},
+ {alias = BYE; command = "quit";},
+ {alias = WI; command = "whois";},
+ {alias = WII; command = "whois $0 $0";},
+ {alias = WW; command = "whowas";},
+ {alias = W; command = "who $C";},
+ {alias = N; command = "names $C";},
+ {alias = M; command = "msg";},
+ {alias = T; command = "topic";},
+ {alias = C; command = "clear";},
+ {alias = CL; command = "clear";},
+ {alias = K; command = "kick";},
+ {alias = KB; command = "kickban";},
+ {alias = KN; command = "knockout";},
+ {alias = B; command = "ban";},
+ {alias = UB; command = "unban";},
+ {alias = IG; command = "ignore";},
+ {alias = UNIG; command = "unignore";},
+ {alias = SB; command = "scrollback";},
+ {alias = UMODE; command = "mode $N";}
+);
+popups = (
+ {label = "<MULTICOMMA>Whois"; command = "/whois %s";},
+ {label = "DCC Send File"; command = "/dcc send %s";},
+ {label = "Open DCC Chat"; command = "/dcc chat %s";},
+ {label = Query; command = "/query %s";},
+ {label = "<MENU><OP>"; command = "Op";},
+ {label = "<MULTI>Op"; command = "/op %s";},
+ {label = "<MULTI>Deop"; command = "/deop %s";},
+ {label = "<MULTI>Voice"; command = "/voice %s";},
+ {label = "<MULTI>Devoice"; command = "/devoice %s";},
+ {label = "<KICK>Kick"; command = "/kick %s %s";},
+ {label = "<MULTI>Ban"; command = "/ban %s";},
+ {label = "<KICK>Kick+ban"; command = "/kickban %s %s";},
+ {label = "<KICK>Knockout"; command = "/knockout %s %s";},
+ {label = "</MENU>"; command = "";},
+ {label = "<MENU>"; command = "CTCP";},
+ {label = Ping; command = "/ping %s";},
+ {label = Version; command = "/ver %s";},
+ {label = "</MENU>"; command = "";}
+);
diff --git a/configure.in b/configure.in
index 03c9c9d2..198a91a6 100644
--- a/configure.in
+++ b/configure.in
@@ -19,10 +19,6 @@ AC_CHECK_HEADERS(string.h stdlib.h unistd.h dirent.h sys/ioctl.h libintl.h)
GNOME_INIT
GNOME_SUPPORT_CHECKS
-AC_ARG_WITH(proplist,
-[ --with-proplist Specify libPropList location],
- proplist_dir=$withval)
-
AC_ARG_WITH(socks,
[ --with-socks Build with socks support],
if test x$withval = xyes; then
@@ -228,29 +224,6 @@ AC_DEFINE(socklen_t, int, Define to 'int' if <sys/socket.h> doesn't define.)
fi
AC_MSG_RESULT($irssi_cv_type_socklen_t)
-
-dnl **
-dnl ** check for libPropList
-dnl **
-
-if test "x$proplist_dir" = "x"; then
- proplib=
-else
- proplib=-L$proplist_dir/lib
-fi
-
-AC_CHECK_LIB(PropList, PLSave, [
- PROG_LIBS="$PROG_LIBS $proplib -lPropList"
- if test "x$proplist_dir" != "x"; then
- CFLAGS="$CFLAGS -I$proplist_dir/include"
- fi
-], [
- echo "ERROR: Irssi needs libPropList for configuration file handling."
- echo "Go get it from http://xlife.dhs.org/irssi/download.php"
- AC_ERROR(["libPropList not found"])
-], $PROG_LIBS $proplib -lPropList)
-
-
dnl **
dnl ** check for socks
dnl **
diff --git a/docs/COMMANDS b/docs/COMMANDS
index 1ebc1b9f..2a01a3bb 100644
--- a/docs/COMMANDS
+++ b/docs/COMMANDS
@@ -318,20 +318,24 @@ LAST [-pub -msgs...] <text>
** Configuration
-SET [key [=value / [key [key..]]
+SET [key [value]]
- Get/set configuration
+ Get/set configuration. Boolean values also need to be changed
+ with ON/OFF/TOGGLE values (not yes/no). Settings aren't saved
+ to disk until you use /SAVE.
-ALIAS, UNALIAS <alias> [command]
+TOGGLE key [ON|OFF]
- Set/remove alias, /unalias is the same as /alias without command
+ Same as /SET <key> TOGGLE, or if ON or OFF parameter is given
+ it will work just like /SET.
+
+SAVE
- These codes are extracted in commands:
- %0 : name of alias
- %1, %2, %3 .. : %th word
- &1, &2, &3 .. : &th word + the rest of the text after it
- %c : channel name
+ Save configuration to disk.
+ALIAS, UNALIAS <alias> [command]
+
+ Set/remove alias, /unalias is the same as /alias without command
Typing extra / before /command (//command) ignores any aliases
IGNORE, UNIGNORE <mask> [level [level..]]
diff --git a/docs/FORMATS b/docs/FORMATS
index f1b9ad30..633b0e42 100644
--- a/docs/FORMATS
+++ b/docs/FORMATS
@@ -14,13 +14,6 @@
%8 Reverse on/off
%9 %_ Bold on/off
%: Insert newline
+ %| Marks the indentation position
%% A single %
-parameter handling:
-
-$[30]1 prints parameter 1 cut/padded to 30 chars
-$[!30]1 prints parameter 1 padded to min. 30 chars
-$[-30]1 prints parameter 1 right aligned
-$[30?]1 prints parameter 1 padded with '?' characters
-$[30.0]1 prints parameter 1 padded with '0' characters
-%| marks the indentation position.
diff --git a/docs/PERL b/docs/PERL
index 37442426..94b83e11 100644
--- a/docs/PERL
+++ b/docs/PERL
@@ -243,7 +243,7 @@ Server server_find_tag(tag)
Server server_find_ircnet(ircnet)
Find first server that is in `ircnet'
-Channel channel_find_any(channel)
+Channel channel_find(channel)
Find `channel' from any server
Channel Server::channel_find_level(level)
@@ -436,6 +436,7 @@ Nick::values()
"nick" - Plain nick
"host" - Host (blah@there.org)
"name" - Real name
+ "hops" - Hop count to the server nick is using
"op", "voice", "gone", "ircop" - 1 or 0
"last_check" - timestamp when last checked gone/ircop status.
"send_massjoin" - Waiting to be sent in a "massjoin" signal - 1 or 0
diff --git a/docs/SIGNALS b/docs/SIGNALS
index 6d92ddb8..7939275c 100644
--- a/docs/SIGNALS
+++ b/docs/SIGNALS
@@ -42,7 +42,7 @@ channels.c:
"channel destroyed", CHANNEL_REC
"channel name changed", CHANNEL_REC
"channel topic changed", CHANNEL_REC
- "channel server changed", CHANNEL_REC
+ "channel server changed", CHANNEL_REC, SERVER_REC *oldserver
"channel query", CHANNEL_REC
"channel wholist", CHANNEL_REC
diff --git a/docs/SPECIAL_VARS b/docs/SPECIAL_VARS
new file mode 100644
index 00000000..e8020558
--- /dev/null
+++ b/docs/SPECIAL_VARS
@@ -0,0 +1,104 @@
+NOTE: This is just a slightly modified file taken from EPIC's help.
+'!' at start of the line means that the feature doesn't work yet..
+
+Special Variables and Expandos
+
+Irssi supports a number of reserved, dynamic variables, sometimes
+referred to as expandos. They are special in that the client is
+constantly updating their values automatically. There are also
+numerous variable modifiers available.
+
+ Modifier Description
+ $variable A normal variable, expanding to the first match of:
+ | 1) an internal SET variable
+ | 2) an environment variable
+ $[num]variable Expands to the variables value, with 'num' width. If
+ | the number is negative, the value is right-aligned.
+ | The value is padded to meet the width with the
+ | character given after number (default is space).
+ | The value is truncated to specified width unless
+ | '!' character precedes the number.
+ $#variable Expands to the number of words in $variable. If $variable
+ | is omitted, it assumes $*
+ $@variable Expands to the number of characters in $variable. if
+ | $variable is omitted, it assumes $*
+ $($subvariable) This is somewhat similar to a pointer, in that the
+ | value of $subvar is taken as the name of the
+ | variable to expand to. Nesting is allowed.
+ ${expression} Permits the value to be embedded in another string
+ | unambiguously.
+! $!history! Expands to a matching entry in the client's command
+ | history, wildcards allowed.
+! $"some text" Uses 'text' as an input prompt, and returns whatever
+ | is typed next. This usage is deprecated, use the
+ | INPUT command instead.
+! $'some text' Same as $"text" except that it only returns the first
+ | next typed character.
+
+Whenever an alias is called, these expandos are set to the arguments passed
+to it. If none of these expandos are used in the alias, or the $() form
+shown above, any arguments passed will automatically be appended to the last
+command in the alias.
+
+ Expando Description
+ $* expands to all arguments passed to an alias
+ $n expands to argument 'n' passed to an alias (counting from zero)
+ $n-m expands to arguments 'n' through 'm' passed to an alias
+ $n- expands to all arguments from 'n' on passed to an alias
+ $-m expands to all arguments up to 'm' passed to an alias
+ $~ expands to the last argument passed to an alias
+
+These variables are set and updated dynamically by the client. The case of
+$A .. $Z is important.
+
+ Variable Description
+! $, last person who sent you a MSG
+! $. last person to whom you sent a MSG
+! $: last person to join a channel you are on
+! $; last person to send a public message to a channel you are on
+ $A text of your AWAY message, if any
+! $B body of last MSG you sent
+ $C current channel
+! $D last person that NOTIFY detected a signon for
+! $E idle time
+! $F time client was started, $time() format
+! $H current server numeric being processed
+! $I channel you were last INVITEd to
+ $J client version text string
+ $K current value of CMDCHARS
+! $L current contents of the input line
+ $M modes of current channel, if any
+ $N current nickname
+! $O value of STATUS_OPER if you are an irc operator
+ $P if you are a channel operator in $C, expands to a '@'
+ $Q nickname of whomever you are QUERYing
+! $R version of current server
+ $S current server name
+ $T target of current input (channel or QUERY nickname)
+! $U value of cutbuffer
+! $V client release date (numeric version string)
+ $W current working directory
+! $X your /userhost $N address (user@host)
+ $Y value of REALNAME
+ $Z time of day (hh:mm)
+ $$ a literal '$'
+
+For example, assume you have the following alias:
+
+ alias blah msg $D Hi there!
+
+If /blah is passed any arguments, they will automatically be appended to the
+MSG text. For example:
+
+ /blah oops /* command as entered */
+ "Hi there! oops" /* text sent to $D */
+
+Another useful form is ${}. In general, variables can be embedded inside
+strings without problems, assuming the surrounding text could not be
+misinterpreted as part of the variable name. This form guarantees that
+surrounding text will not affect the expression's return value.
+
+ /eval echo foo$Nfoo /* breaks, looks for $nfoo */
+ /eval echo foo${N}foo /* ${N} returns current nickname */
+ fooYourNickfoo /* returned by above command */
+
diff --git a/irssi.spec.in b/irssi.spec.in
index af7246ec..3a00902f 100644
--- a/irssi.spec.in
+++ b/irssi.spec.in
@@ -1,4 +1,4 @@
-# $Revision: 1.7 $, $Date: 2000/02/25 17:03:15 $
+# $Revision: 1.8 $, $Date: 2000/04/14 11:27:02 $
Name: irssi
Version: @VERSION@
Release: 1
@@ -10,7 +10,6 @@ Group: Applications/Communications
Group(pl): Aplikacje/Komunikacja
URL: http://xlife.dhs.org/irssi/
Source0: http://xlife.dhs.org/irssi/files/%{name}-%{version}.tar.gz
-BuildRequires: libPropList
BuildRequires: glib-devel
BuildRequires: ncurses-devel
BuildRequires: imlib-devel
@@ -63,7 +62,6 @@ LDFLAGS="-s -L/usr/X11R6/lib"; export LDFLAGS
--with-imlib \
--enable-ipv6 \
--with-textui=ncurses \
- --with-proplist \
--without-socks \
--with-plugins
make
@@ -110,6 +108,56 @@ rm -rf $RPM_BUILD_ROOT
All below listed persons can be reached on <cvs_login>@pld.org.pl
$Log: irssi.spec.in,v $
+Revision 1.8 2000/04/14 11:27:02 cras
+Sorry for a big update - I still don't have internet connection at home
+and this is what I've been doing a few weeks now.. :) You really shouldn't
+upgrade to this version without keeping a backup of the working one, since
+this will break everything and at least notify list is broken - probably
+something else too.
+
+* On the way to 0.8.0 .. Major rewriting/rearranging code. There's
+ some changes in behaviour because I'm trying to make Irssi a bit
+ more compatible with EPIC.
+
+* libPropList isn't needed anymore - I'm using my own configuration
+ library. This is mostly because different proplists worked a bit
+ differently everywhere and several people had problems with it.
+ It's also yet another extra library that you needed to compile
+ Irssi. New configuration library has several advantages:
+
+ You can add comments to configuration file and they also stay
+ there when it's saved.
+
+ It's not nearly as vulnerable as proplist. If some error occurs,
+ instead of just not reading anything it will try to continue if
+ possible. Also the error messages are written to irssi's text
+ window instead of stdout.
+
+ It can be managed more easily than proplist - setting/getting the
+ configuration is a lot more easier.
+
+* Coding style changes - I'm not using gint, gchar etc. anymore,
+ they're just extra pain when moving code to non-glib projects and
+ syntax hilighting doesn't work by default with most editors ;)
+
+ Indentation style was also changed to K&R because of some political
+ reasons ;) And I'm already starting to like it.. :) It forces me
+ to split code to different functions more often and the result is
+ that the code gets more readable.
+
+ And finally I'm also using nst' all over the place.
+
++ /EVAL <commands> - Expand all the special variables from string and
+ run it. Commands can be split with ; character. See
+ docs/SPECIAL_VARS for more info.
++ Aliases are parsed just like /EVAL - arguments are in $0..$9.
++ Text formats are also parsed like /EVAL, arguments used to be in
+ $1..$9, now they're in $0..$8 so it messes up existing themes..
++ /SET [key [value]] - no more the '=' character. Boolean values
+ also need to be changed with ON/OFF/TOGGLE values (not yes/no).
+ Settings aren't saved to disk until you use /SAVE.
++ /TOGGLE <key> [ON/OFF] - same as /SET <key> TOGGLE
+
Revision 1.7 2000/02/25 17:03:15 cras
Irssi 0.7.27 released.
diff --git a/src/Makefile.am b/src/Makefile.am
index 90006689..7f8b82f1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,4 +17,4 @@ noinst_HEADERS = \
irssi-plugin.h \
irssi-plugin-gui.h
-SUBDIRS = irc-base irc-extra ui-common lib-config lib-popt settings $(GNOMEUI) $(TEXTUI) $(BOTUI)
+SUBDIRS = lib-popt lib-config irc-base irc-extra ui-common $(GNOMEUI) $(TEXTUI) $(BOTUI)
diff --git a/src/common-setup.h b/src/common-setup.h
index 42a60fa5..e04ca4f0 100644
--- a/src/common-setup.h
+++ b/src/common-setup.h
@@ -1,11 +1,7 @@
#ifndef __COMMON_SETUP_H
#define __COMMON_SETUP_H
-#include "irc-base/network.h"
-#include "settings/settings-public.h"
-
#define LOG_FILE_CREATE_MODE 0644
-#define CMD_CHAR '/'
/* wait for half an hour before trying to reconnect to host where last
connection failed */
@@ -26,59 +22,7 @@
/* Maximum time to wait for more JOINs before sending massjoin signal */
#define MAX_MASSJOIN_WAIT 5000
-/* lists */
-extern GSList *aliases, *ignores, *completions, *notifies, *hilights, *replaces, *popups;
-
-/* servers */
-typedef struct {
- char *server;
- int port;
-
- char *ircnet;
- char *password;
- int autoconnect;
- int cmd_queue_speed; /* override the default if > 0 */
-
- char *own_address; /* address to use when connecting this server */
- IPADDR own_ip; /* resolved own_address or full of zeros */
-
- time_t last_connect; /* to avoid reconnecting too fast.. */
- int last_failed; /* if last connection attempt failed */
-} SETUP_SERVER_REC;
-
-typedef struct {
- char *name;
-
- char *nick;
- char *username;
- char *realname;
-
- /* max. number of kicks/msgs/mode changes per command */
- int max_kicks, max_msgs, max_modes;
-} IRCNET_REC;
-
-extern GSList *setupservers; /* list of local servers */
-extern GSList *ircnets; /* list of available ircnets */
-
-/* channels */
-typedef struct {
- int autojoin;
-
- char *name;
- char *ircnet;
- char *password;
-
- char *botmasks;
- char *autosendcmd;
-
- char *background;
- char *font;
-} SETUP_CHANNEL_REC;
-
-extern GSList *setupchannels;
-
-extern gboolean readonly;
-extern IPADDR source_host_ip; /* Resolved address */
-extern gboolean source_host_ok; /* Use source_host_ip .. */
+/* How long to keep netsplits in memory (seconds) */
+#define NETSPLIT_MAX_REMEMBER (60*30)
#endif
diff --git a/src/common.h b/src/common.h
index 3fa42b2d..e3dde2c6 100644
--- a/src/common.h
+++ b/src/common.h
@@ -23,8 +23,6 @@
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
-#include <sys/wait.h>
-#include <sys/utsname.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
@@ -38,10 +36,14 @@
#include <gmodule.h>
#include "irc-base/memdebug.h"
-#include "lib-config/irssi-config.h"
-#include "common-setup.h"
#include "nls.h"
+#define g_free_not_null(a) \
+ if (a) g_free(a);
+
+#define g_free_and_null(a) \
+ if (a) { g_free(a); (a) = NULL; }
+
typedef enum
{
G_INPUT_READ = 1 << 0,
diff --git a/src/lib-config/Makefile.am b/src/lib-config/Makefile.am
index 7de63122..00969218 100644
--- a/src/lib-config/Makefile.am
+++ b/src/lib-config/Makefile.am
@@ -1,9 +1,15 @@
noinst_LTLIBRARIES = libirssi_config.la
-INCLUDES = $(GLIB_CFLAGS)
+INCLUDES = \
+ $(GLIB_CFLAGS) \
+ -I$(top_srcdir)/src
libirssi_config_la_SOURCES = \
- irssi-config.c
+ get.c \
+ set.c \
+ parse.c \
+ write.c
noinst_HEADERS = \
- irssi-config.h
+ iconfig.h \
+ module.h
diff --git a/src/lib-config/get.c b/src/lib-config/get.c
new file mode 100644
index 00000000..e29df699
--- /dev/null
+++ b/src/lib-config/get.c
@@ -0,0 +1,256 @@
+/*
+ get.c : irssi configuration - get settings from memory
+
+ 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"
+
+CONFIG_NODE *config_node_find(CONFIG_NODE *node, const char *key)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(node != NULL, NULL);
+ g_return_val_if_fail(key != NULL, NULL);
+ g_return_val_if_fail(is_node_list(node), NULL);
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+
+ if (node->key != NULL && g_strcasecmp(node->key, key) == 0)
+ return node;
+ }
+
+ return NULL;
+}
+
+/* find the section from node - if not found create it unless new_type is -1.
+ you can also specify in new_type if it's NODE_TYPE_LIST or NODE_TYPE_BLOCK */
+CONFIG_NODE *config_node_section(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int new_type)
+{
+ CONFIG_NODE *node;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+ g_return_val_if_fail(parent != NULL, NULL);
+ g_return_val_if_fail(is_node_list(parent), NULL);
+
+ node = key == NULL ? NULL : config_node_find(parent, key);
+ if (node != NULL) {
+ g_return_val_if_fail(new_type == -1 || new_type == node->type, NULL);
+ return node;
+ }
+
+ if (new_type == -1)
+ return NULL;
+
+ node = g_new0(CONFIG_NODE, 1);
+ parent->value = g_slist_append(parent->value, node);
+
+ node->type = new_type;
+ node->key = key == NULL ? NULL : g_strdup(key);
+
+ return node;
+}
+
+/* find the section with the whole path.
+ create the path if necessary `create' is TRUE. */
+CONFIG_NODE *config_node_traverse(CONFIG_REC *rec, const char *section, int create)
+{
+ CONFIG_NODE *node;
+ char **list, **tmp;
+ int is_list, new_type;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+
+ if (section == NULL || *section == '\0')
+ return rec->mainnode;
+
+ /* check if it already exists in cache */
+ node = g_hash_table_lookup(rec->cache, section);
+ if (node != NULL) return node;
+
+ new_type = -1;
+
+ node = rec->mainnode;
+ list = g_strsplit(section, "/", -1);
+ for (tmp = list; *tmp != NULL; tmp++) {
+ is_list = **tmp == '(';
+ if (create) new_type = is_list ? NODE_TYPE_LIST : NODE_TYPE_BLOCK;
+
+ node = config_node_section(rec, node, *tmp + is_list, new_type);
+ if (node == NULL) return NULL;
+ }
+ g_strfreev(list);
+
+ /* save to cache */
+ g_hash_table_insert(rec->cache, g_strdup(section), node);
+ return node;
+}
+
+char *config_get_str(CONFIG_REC *rec, const char *section, const char *key, const char *def)
+{
+ CONFIG_NODE *parent, *node;
+ char *path;
+
+ g_return_val_if_fail(rec != NULL, (char *) def);
+ g_return_val_if_fail(section != NULL, (char *) def);
+ g_return_val_if_fail(key != NULL, (char *) def);
+
+ /* check if it already exists in cache */
+ path = g_strconcat(section, "/", key, NULL);
+ node = g_hash_table_lookup(rec->cache, path);
+
+ if (node != NULL)
+ g_free(path);
+ else {
+ parent = config_node_traverse(rec, section, FALSE);
+ node = parent == NULL ? NULL :
+ config_node_find(parent, key);
+
+ /* save to cache */
+ if (node != NULL)
+ g_hash_table_insert(rec->cache, path, node);
+ else
+ g_free(path);
+ }
+
+ return (node == NULL || !has_node_value(node)) ? (char *) def : node->value;
+}
+
+int config_get_int(CONFIG_REC *rec, const char *section, const char *key, int def)
+{
+ char *str;
+
+ str = config_get_str(rec, section, key, NULL);
+ if (str == NULL) return def;
+
+ return atoi(str);
+}
+
+int config_get_bool(CONFIG_REC *rec, const char *section, const char *key, int def)
+{
+ char *str;
+
+ str = config_get_str(rec, section, key, NULL);
+ if (str == NULL) return def;
+
+ return toupper(*str) == 'T' || toupper(*str) == 'Y';
+}
+
+/* Return value of key `value_key' from list item where `key' is `value' */
+const char *config_list_find(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key)
+{
+ CONFIG_NODE *node;
+
+ node = config_list_find_node(rec, section, key, value, value_key);
+ return node != NULL && node->type == NODE_TYPE_KEY ?
+ node->value : NULL;
+}
+
+/* Like config_list_find(), but return node instead of it's value */
+CONFIG_NODE *config_list_find_node(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key)
+{
+ CONFIG_NODE *node, *keynode;
+ GSList *tmp;
+
+ g_return_val_if_fail(rec != NULL, NULL);
+ g_return_val_if_fail(section != NULL, NULL);
+ g_return_val_if_fail(key != NULL, NULL);
+ g_return_val_if_fail(value_key != NULL, NULL);
+
+ node = config_node_traverse(rec, section, FALSE);
+ if (node == NULL || !is_node_list(node)) return NULL;
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ /* key matches value? */
+ keynode = config_node_find(node, key);
+ if (keynode == NULL || keynode->type != NODE_TYPE_KEY ||
+ g_strcasecmp(keynode->value, value) != 0) continue;
+
+ return config_node_find(node, value_key);
+ }
+
+ return NULL;
+}
+
+char *config_node_get_str(CONFIG_NODE *parent, const char *key, const char *def)
+{
+ CONFIG_NODE *node;
+
+ node = config_node_find(parent, key);
+ return (node == NULL || !has_node_value(node)) ? def : node->value;
+}
+
+int config_node_get_int(CONFIG_NODE *parent, const char *key, int def)
+{
+ char *str;
+
+ str = config_node_get_str(parent, key, NULL);
+ if (str == NULL) return def;
+
+ return atoi(str);
+}
+
+int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def)
+{
+ char *str;
+
+ str = config_node_get_str(parent, key, NULL);
+ if (str == NULL) return def;
+
+ return toupper(*str) == 'T' || toupper(*str) == 'Y' ||
+ (toupper(*str) == 'O' && toupper(str[1]) == 'N');
+}
+
+/* Get the value of keys `key' and `key_value' and put them to
+ `ret_key' and `ret_value'. Returns -1 if not found. */
+int config_node_get_keyvalue(CONFIG_NODE *node, const char *key, const char *value_key, char **ret_key, char **ret_value)
+{
+ CONFIG_NODE *keynode, *valuenode;
+ GSList *tmp;
+
+ g_return_val_if_fail(node != NULL, -1);
+ g_return_val_if_fail(key != NULL, -1);
+ g_return_val_if_fail(value_key != NULL, -1);
+ g_return_val_if_fail(ret_key != NULL, -1);
+ g_return_val_if_fail(ret_value != NULL, -1);
+
+ for (tmp = node->value; tmp != NULL; tmp = tmp->next) {
+ node = tmp->data;
+
+ if (node->type != NODE_TYPE_BLOCK)
+ continue;
+
+ keynode = config_node_find(node, key);
+ if (keynode == NULL || keynode->type != NODE_TYPE_KEY)
+ continue;
+
+ valuenode = config_node_find(node, value_key);
+
+ *ret_key = keynode->key;
+ *ret_value = valuenode != NULL && valuenode->type == NODE_TYPE_KEY ?
+ valuenode->value : NULL;
+ return 0;
+ }
+
+ return -1;
+}
diff --git a/src/lib-config/iconfig.h b/src/lib-config/iconfig.h
new file mode 100644
index 00000000..bbee0f6e
--- /dev/null
+++ b/src/lib-config/iconfig.h
@@ -0,0 +1,136 @@
+#ifndef __ICONFIG_H
+#define __ICONFIG_H
+
+enum {
+ NODE_TYPE_KEY,
+ NODE_TYPE_VALUE,
+ NODE_TYPE_BLOCK,
+ NODE_TYPE_LIST,
+ NODE_TYPE_COMMENT,
+};
+
+#define has_node_value(a) \
+ ((a)->type == NODE_TYPE_KEY || (a)->type == NODE_TYPE_VALUE)
+#define is_node_list(a) \
+ ((a)->type == NODE_TYPE_BLOCK || (a)->type == NODE_TYPE_LIST)
+
+typedef struct {
+ int type;
+ char *key;
+ void *value;
+} CONFIG_NODE;
+
+/* a = { x=y; y=z; }
+
+ node1: type = NODE_TYPE_BLOCK, key = "a", value = (GSList *) nodes
+ nodes: (node2, node3)
+ node2: type = NODE_TYPE_KEY, key = "x", value = (char *) "y"
+ node3: type = NODE_TYPE_KEY, key = "y", value = (char *) "z"
+
+ b = ( a, { b=c; d=e; } )
+
+ node1: type = NODE_TYPE_LIST, key = "b", value = (GSList *) nodes
+ nodes: (node2, node3)
+ node2: type = NODE_TYPE_VALUE, key = NULL, value = (char *) "a"
+ node4: type = NODE_TYPE_BLOCK, key = NULL, value = (GSList *) nodes2
+ nodes2: (node4, node5)
+ node4: type = NODE_TYPE_KEY, key = "b", value = (char *) "c"
+ node5: type = NODE_TYPE_KEY, key = "d", value = (char *) "e"
+
+ Comments node has key=NULL and value is the comment line. Empty lines are
+ also in comments so they won't be forgotten when the config file is
+ written.
+
+*/
+
+struct _config_rec {
+ char *fname;
+ int handle;
+ int create_mode;
+
+ char *last_error;
+ CONFIG_NODE *mainnode;
+ GHashTable *cache;
+
+ GScanner *scanner;
+
+ /* while writing to configuration file.. */
+ int tmp_indent_level; /* indentation position */
+ int tmp_last_lf; /* last character was a line feed */
+};
+
+typedef struct _config_rec CONFIG_REC;
+
+/* Open configuration. The file is created if it doesn't exist, unless
+ `create_mode' is -1. `fname' can be NULL if you just want to use
+ config_parse_data() */
+CONFIG_REC *config_open(const char *fname, int create_mode);
+/* Release all memory used by configuration */
+void config_close(CONFIG_REC *rec);
+/* Change file name of config file */
+void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode);
+
+/* Parse configuration file */
+int config_parse(CONFIG_REC *rec);
+/* Parse configuration found from `data'. `input_name' is specifies the
+ "configuration name" which is displayed in error messages. */
+int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name);
+
+/* Write configuration file. Write to `fname' if it's not NULL.
+ If `create_mode' is -1, use the one that was given to config_open(). */
+int config_write(CONFIG_REC *rec, const char *fname, int create_mode);
+
+#define config_last_error(rec) \
+ (rec)->last_error
+
+/* Getting values
+
+ `section' is something like "maingroup/key/subkey", or with lists
+ "maingroup/(list/subkey"
+
+ `def' is returned if the value is not found. */
+char *config_get_str(CONFIG_REC *rec, const char *section, const char *key, const char *def);
+int config_get_int(CONFIG_REC *rec, const char *section, const char *key, int def);
+int config_get_bool(CONFIG_REC *rec, const char *section, const char *key, int def);
+
+/* Return value of key `value_key' from list item where `key' is `value' */
+const char *config_list_find(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key);
+/* Like config_list_find(), but return node instead of it's value */
+CONFIG_NODE *config_list_find_node(CONFIG_REC *rec, const char *section, const char *key, const char *value, const char *value_key);
+
+/* Setting values */
+int config_set_str(CONFIG_REC *rec, const char *section, const char *key, const char *value);
+int config_set_int(CONFIG_REC *rec, const char *section, const char *key, int value);
+int config_set_bool(CONFIG_REC *rec, const char *section, const char *key, int value);
+
+/* Handling the configuration directly with nodes -
+ useful when you need to read all values in a block/list. */
+CONFIG_NODE *config_node_find(CONFIG_NODE *node, const char *key);
+/* Find the section from node - if not found create it unless new_type is -1.
+ You can also specify in new_type if it's NODE_TYPE_LIST or NODE_TYPE_BLOCK */
+CONFIG_NODE *config_node_section(CONFIG_REC *rec, CONFIG_NODE *parent, const char *key, int new_type);
+/* Find the section with the whole path.
+ Create the path if necessary `create' is TRUE. */
+CONFIG_NODE *config_node_traverse(CONFIG_REC *rec, const char *section, int create);
+/* Get the value of keys `key' and `key_value' and put them to
+ `ret_key' and `ret_value'. Returns -1 if not found. */
+int config_node_get_keyvalue(CONFIG_NODE *node, const char *key, const char *value_key, char **ret_key, char **ret_value);
+
+char *config_node_get_str(CONFIG_NODE *parent, const char *key, const char *def);
+int config_node_get_int(CONFIG_NODE *parent, const char *key, int def);
+int config_node_get_bool(CONFIG_NODE *parent, const char *key, int def);
+
+void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value);
+void config_node_set_int(CONFIG_NODE *parent, const char *key, int value);
+void config_node_set_bool(CONFIG_NODE *parent, const char *key, int value);
+
+/* add/change the value of the `key' */
+void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value);
+/* remove one node from block/list.
+ ..set_str() with value = NULL does the same. */
+void config_node_remove(CONFIG_NODE *parent, CONFIG_NODE *node);
+
+/* clear the entire configuration */
+void config_nodes_remove_all(CONFIG_REC *rec);
+
+#endif
diff --git a/src/lib-config/irssi-config.c b/src/lib-config/irssi-config.c
deleted file mode 100644
index 2750397c..00000000
--- a/src/lib-config/irssi-config.c
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- config.c : Functions for reading onfiguration 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 "../common.h"
-#include "../irc-base/irc-base.h"
-
-proplist_t cprop = NULL;
-
-gboolean config_get_bool(proplist_t prop, gchar *key, gboolean def)
-{
- proplist_t pkey, pvalue;
- gchar *value;
-
- if (prop == NULL)
- return def;
-
- pkey = PLMakeString(key);
- pvalue = PLGetDictionaryEntry(prop, pkey);
- PLRelease(pkey);
- if (pvalue == NULL) return def;
-
- value = PLGetString(pvalue);
- return toupper(*value) == 'T' || toupper(*value) == 'Y';
-}
-
-gint config_get_int(proplist_t prop, gchar *key, gint def)
-{
- proplist_t pkey, pvalue;
- gint num;
-
- if (prop == NULL)
- return def;
-
- pkey = PLMakeString(key);
- pvalue = PLGetDictionaryEntry(prop, pkey);
- PLRelease(pkey);
- if (pvalue == NULL) return def;
-
- return sscanf(PLGetString(pvalue), "%d", &num) != 1 ? def : num;
-}
-
-gchar *config_get_str(proplist_t prop, gchar *key, gchar *def)
-{
- proplist_t pkey, pvalue;
-
- if (prop == NULL)
- return def;
-
- pkey = PLMakeString(key);
- pvalue = PLGetDictionaryEntry(prop, pkey);
- PLRelease(pkey);
-
- return pvalue == NULL ? def : PLGetString(pvalue);
-}
-
-proplist_t config_get_prop(proplist_t prop, gchar *key)
-{
- proplist_t ret, pkey;
-
- pkey = PLMakeString(key);
- ret = PLGetDictionaryEntry(prop, pkey);
- PLRelease(pkey);
-
- return ret;
-}
-
-proplist_t config_make_dict(proplist_t prop, gchar *section)
-{
- proplist_t psect, pkey;
-
- pkey = PLMakeString(section);
- psect = PLMakeDictionaryFromEntries(NULL, NULL);
- prop = PLInsertDictionaryEntry(prop, pkey, psect);
- return prop;
-}
-
-proplist_t config_set_str(proplist_t prop, gchar *key, gchar *value)
-{
- proplist_t pkey, pvalue;
-
- pkey = PLMakeString(key); pvalue = PLMakeString(value);
- prop = PLInsertDictionaryEntry(prop, pkey, pvalue);
- PLRelease(pkey); PLRelease(pvalue);
- return prop;
-}
-
-proplist_t config_set_int(proplist_t prop, gchar *key, gint value)
-{
- proplist_t pkey, pvalue;
- gchar *strval;
-
- strval = g_strdup_printf("%d", value);
- pkey = PLMakeString(key); pvalue = PLMakeString(strval);
- prop = PLInsertDictionaryEntry(prop, pkey, pvalue);
- PLRelease(pkey); PLRelease(pvalue);
- g_free(strval);
- return prop;
-}
-
-proplist_t config_set_bool(proplist_t prop, gchar *key, gboolean value)
-{
- proplist_t pkey, pvalue;
-
- pkey = PLMakeString(key); pvalue = PLMakeString(value ? "Yes" : "No");
- prop = PLInsertDictionaryEntry(prop, pkey, pvalue);
- PLRelease(pkey); PLRelease(pvalue);
- return prop;
-}
-
-proplist_t config_clean_key(proplist_t prop, gchar *key)
-{
- proplist_t pkey;
-
- pkey = PLMakeString(key);
- PLRemoveDictionaryEntry(prop, pkey);
- PLRelease(pkey);
- return prop;
-}
-
-proplist_t config_section(proplist_t *prop, gchar *section)
-{
- proplist_t ret, pkey;
-
- pkey = PLMakeString(section);
- ret = PLGetDictionaryEntry(*prop, pkey);
- if (ret == NULL)
- {
- ret = PLMakeDictionaryFromEntries(NULL, NULL);
- *prop = PLInsertDictionaryEntry(*prop, pkey, ret);
- }
- PLRelease(pkey);
-
- return ret;
-}
-
-proplist_t config_list_section(proplist_t *prop, gchar *section)
-{
- proplist_t ret, pkey;
-
- pkey = PLMakeString(section);
- ret = PLGetDictionaryEntry(*prop, pkey);
- if (ret == NULL)
- {
- ret = PLMakeArrayFromElements(NULL);
- *prop = PLInsertDictionaryEntry(*prop, pkey, ret);
- }
- PLRelease(pkey);
-
- return ret;
-}
-
-gint config_list_find(proplist_t prop, gchar *key, gchar *value)
-{
- proplist_t item;
- gint num, max;
- gchar *ret;
-
- if (prop == NULL)
- return -1;
-
- max = PLGetNumberOfElements(prop);
- for (num = 0; num < max; num++)
- {
- item = PLGetArrayElement(prop, num);
- ret = config_get_str(item, key, NULL);
- if (ret != NULL && g_strcasecmp(ret, value) == 0)
- return num;
- }
-
- return -1;
-}
-
diff --git a/src/lib-config/irssi-config.h b/src/lib-config/irssi-config.h
deleted file mode 100644
index 2dc06c5e..00000000
--- a/src/lib-config/irssi-config.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef __IRSSI_CONFIG_H
-#define __IRSSI_CONFIG_H
-
-#include <proplist.h>
-
-extern proplist_t cprop;
-
-/* make proplist handling easier */
-gchar *config_get_str(proplist_t prop, gchar *key, gchar *def);
-gint config_get_int(proplist_t prop, gchar *key, gint def);
-gboolean config_get_bool(proplist_t prop, gchar *key, gboolean def);
-proplist_t config_get_prop(proplist_t prop, gchar *key);
-
-proplist_t config_set_str(proplist_t prop, gchar *key, gchar *value);
-proplist_t config_set_int(proplist_t prop, gchar *key, gint value);
-proplist_t config_set_bool(proplist_t prop, gchar *key, gboolean value);
-
-proplist_t config_section(proplist_t *prop, gchar *section);
-proplist_t config_list_section(proplist_t *prop, gchar *section);
-proplist_t config_make_dict(proplist_t prop, gchar *section);
-proplist_t config_clean_key(proplist_t prop, gchar *key);
-
-gint config_list_find(proplist_t prop, gchar *key, gchar *value);
-
-#endif
diff --git a/src/lib-config/module.h b/src/lib-config/module.h
new file mode 100644
index 00000000..22e0e7c7
--- /dev/null
+++ b/src/lib-config/module.h
@@ -0,0 +1,6 @@
+#include "common.h"
+#include "iconfig.h"
+
+/* private */
+int config_error(CONFIG_REC *rec, const char *msg);
+
diff --git a/src/lib-config/parse.c b/src/lib-config/parse.c
new file mode 100644
index 00000000..5171161c
--- /dev/null
+++ b/src/lib-config/parse.c
@@ -0,0 +1,335 @@
+/*
+ parse.c : irssi configuration - parse 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"
+
+int config_error(CONFIG_REC *rec, const char *msg)
+{
+ g_free_and_null(rec->last_error);
+ rec->last_error = g_strdup(msg);
+ return -1;
+}
+
+static int node_add_comment(CONFIG_NODE *parent, const char *str)
+{
+ CONFIG_NODE *node;
+
+ g_return_val_if_fail(parent != NULL, -1);
+
+ if (!is_node_list(parent))
+ return -1;
+
+ node = g_new0(CONFIG_NODE, 1);
+ node->type = NODE_TYPE_COMMENT;
+ node->value = str == NULL ? NULL : g_strdup(str);
+
+ parent->value = g_slist_append(parent->value, node);
+ return 0;
+}
+
+/* same as g_scanner_get_next_token() except skips and reads the comments */
+static void config_parse_get_token(GScanner *scanner, CONFIG_NODE *node)
+{
+ int prev_empty = FALSE;
+
+ for (;;) {
+ g_scanner_get_next_token(scanner);
+
+ if (scanner->token == G_TOKEN_COMMENT_SINGLE)
+ node_add_comment(node, scanner->value.v_string);
+ else if (scanner->token == '\n') {
+ if (prev_empty) node_add_comment(node, NULL);
+ } else {
+ if (scanner->token == G_TOKEN_INT) {
+ scanner->token = G_TOKEN_STRING;
+#undef g_strdup_printf /* This is free'd by GLib itself */
+ scanner->value.v_string = g_strdup_printf("%lu", scanner->value.v_int);
+#ifdef MEM_DEBUG
+#define g_strdup_printf ig_strdup_printf
+#endif
+ }
+ break;
+ }
+
+ prev_empty = TRUE;
+ }
+}
+
+/* same as g_scanner_peek_next_token() except skips and reads the comments */
+static void config_parse_peek_token(GScanner *scanner, CONFIG_NODE *node)
+{
+ int prev_empty = FALSE;
+
+ for (;;) {
+ g_scanner_peek_next_token(scanner);
+
+ if (scanner->next_token == G_TOKEN_COMMENT_SINGLE)
+ node_add_comment(node, scanner->next_value.v_string);
+ else if (scanner->next_token == '\n') {
+ if (prev_empty) node_add_comment(node, NULL);
+ } else
+ break;
+
+ prev_empty = TRUE;
+ g_scanner_get_next_token(scanner);
+ }
+}
+
+/* get optional token, optionally warn if it's missing */
+static void config_parse_warn_missing(CONFIG_REC *rec, CONFIG_NODE *node, int expected_token, int print_warning)
+{
+ config_parse_peek_token(rec->scanner, node);
+ if (rec->scanner->next_token == expected_token) {
+ g_scanner_get_next_token(rec->scanner);
+ return;
+ }
+
+ if (print_warning)
+ g_scanner_warn(rec->scanner, "Warning: missing '%c'", expected_token);
+}
+
+static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, int expect);
+
+static int config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node)
+{
+ CONFIG_NODE *newnode;
+ int print_warning;
+ char *key, last_char;
+
+ g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR);
+ g_return_val_if_fail(node != NULL, G_TOKEN_ERROR);
+
+ config_parse_get_token(rec->scanner, node);
+
+ last_char = node->type == NODE_TYPE_LIST ? ',' : ';';
+
+ /* key */
+ key = NULL;
+ if (node->type != NODE_TYPE_LIST &&
+ (rec->scanner->token == G_TOKEN_STRING)) {
+ key = g_strdup(rec->scanner->value.v_string);
+
+ config_parse_get_token(rec->scanner, node);
+ if (rec->scanner->token != '=')
+ return '=';
+
+ config_parse_get_token(rec->scanner, node);
+ }
+
+ switch (rec->scanner->token) {
+ case G_TOKEN_STRING:
+ /* value */
+ config_node_set_str(node, key, rec->scanner->value.v_string);
+ g_free_not_null(key);
+
+ print_warning = TRUE;
+ if (node->type == NODE_TYPE_LIST) {
+ /* if it's last item it doesn't need comma */
+ config_parse_peek_token(rec->scanner, node);
+ if (rec->scanner->next_token == ')')
+ print_warning = FALSE;
+ }
+
+ config_parse_warn_missing(rec, node, last_char, print_warning);
+ break;
+
+ case '{':
+ /* block */
+ if (key == NULL && node->type != NODE_TYPE_LIST)
+ return G_TOKEN_ERROR;
+
+ newnode = config_node_section(rec, node, key, NODE_TYPE_BLOCK);
+ config_parse_loop(rec, newnode, '}');
+ g_free_not_null(key);
+
+ config_parse_get_token(rec->scanner, node);
+ if (rec->scanner->token != '}')
+ return '}';
+
+ config_parse_warn_missing(rec, node, last_char, FALSE);
+ break;
+
+ case '(':
+ /* list */
+ if (key == NULL)
+ return G_TOKEN_ERROR;
+ newnode = config_node_section(rec, node, key, NODE_TYPE_LIST);
+ config_parse_loop(rec, newnode, ')');
+ g_free_not_null(key);
+
+ config_parse_get_token(rec->scanner, node);
+ if (rec->scanner->token != ')')
+ return ')';
+
+ config_parse_warn_missing(rec, node, last_char, FALSE);
+ break;
+
+ default:
+ /* error */
+ g_free_not_null(key);
+ return G_TOKEN_STRING;
+ }
+
+ return G_TOKEN_NONE;
+}
+
+static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, int expect)
+{
+ int expected_token;
+
+ g_return_if_fail(rec != NULL);
+ g_return_if_fail(node != NULL);
+
+ do {
+ expected_token = config_parse_symbol(rec, node);
+ if (expected_token != G_TOKEN_NONE) {
+ if (expected_token == G_TOKEN_ERROR)
+ expected_token = G_TOKEN_NONE;
+ g_scanner_unexp_token(rec->scanner, expected_token, NULL, "symbol", NULL, NULL, TRUE);
+ }
+
+ config_parse_peek_token(rec->scanner, node);
+ } while (rec->scanner->next_token != expect &&
+ rec->scanner->next_token != G_TOKEN_EOF);
+}
+
+static void config_parse_error_func(GScanner *scanner, char *message, int is_error)
+{
+ CONFIG_REC *rec = scanner->user_data;
+ char *old;
+
+ old = rec->last_error;
+ rec->last_error = g_strdup_printf("%s%s:%d: %s%s\n",
+ old == NULL ? "" : old,
+ scanner->input_name, scanner->line,
+ is_error ? "error: " : "",
+ message);
+ g_free_not_null(old);
+}
+
+void config_parse_init(CONFIG_REC *rec, const char *name)
+{
+ GScanner *scanner;
+
+ g_free_and_null(rec->last_error);
+ config_nodes_remove_all(rec);
+
+ rec->scanner = scanner = g_scanner_new(NULL);
+ scanner->config->skip_comment_single = FALSE;
+ scanner->config->cset_skip_characters = " \t";
+ scanner->config->scan_binary = FALSE;
+ scanner->config->scan_octal = FALSE;
+ scanner->config->scan_float = FALSE;
+ scanner->config->scan_string_sq = TRUE;
+ scanner->config->scan_string_dq = TRUE;
+ scanner->config->scan_identifier_1char = TRUE;
+ scanner->config->identifier_2_string = TRUE;
+
+ scanner->user_data = rec;
+ scanner->input_name = name;
+ scanner->msg_handler = (GScannerMsgFunc) config_parse_error_func;
+}
+
+/* Parse configuration file */
+int config_parse(CONFIG_REC *rec)
+{
+ g_return_val_if_fail(rec != NULL, -1);
+ g_return_val_if_fail(rec->fname != NULL, -1);
+
+ rec->handle = open(rec->fname, O_RDONLY);
+ if (rec->handle == -1)
+ return config_error(rec, g_strerror(errno));
+
+ config_parse_init(rec, rec->fname);
+ g_scanner_input_file(rec->scanner, rec->handle);
+ config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF);
+ g_scanner_destroy(rec->scanner);
+
+ close(rec->handle);
+ rec->handle = -1;
+
+ return rec->last_error == NULL ? 0 : -1;
+}
+
+/* Parse configuration found from `data'. `input_name' is specifies the
+ "configuration name" which is displayed in error messages. */
+int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name)
+{
+ config_parse_init(rec, input_name);
+ g_scanner_input_text(rec->scanner, data, strlen(data));
+ config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF);
+ g_scanner_destroy(rec->scanner);
+
+ return rec->last_error == NULL ? 0 : -1;
+}
+
+/* Open configuration. The file is created if it doesn't exist, unless
+ `create_mode' is -1. `fname' can be NULL if you just want to use
+ config_parse_data() */
+CONFIG_REC *config_open(const char *fname, int create_mode)
+{
+ CONFIG_REC *rec;
+ int f;
+
+ if (fname != NULL) {
+ f = open(fname, O_RDONLY | (create_mode != -1 ? O_CREAT : 0), create_mode);
+ if (f == -1) return NULL;
+ close(f);
+ }
+
+ rec = g_new0(CONFIG_REC, 1);
+ rec->fname = fname == NULL ? NULL : g_strdup(fname);
+ rec->handle = -1;
+ rec->create_mode = create_mode;
+ rec->mainnode = g_new0(CONFIG_NODE, 1);
+ rec->mainnode->type = NODE_TYPE_BLOCK;
+ rec->cache = g_hash_table_new((GHashFunc) g_str_hash, (GCompareFunc) g_str_equal);
+
+ return rec;
+}
+
+/* Release all memory used by configuration */
+void config_close(CONFIG_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ config_nodes_remove_all(rec);
+ g_free(rec->mainnode);
+
+ if (rec->handle != -1) close(rec->handle);
+ g_hash_table_foreach(rec->cache, (GHFunc) g_free, NULL);
+ g_hash_table_destroy(rec->cache);
+ g_free_not_null(rec->last_error);
+ g_free(rec->fname);
+ g_free(rec);
+}
+
+/* Change file name of config file */
+void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode)
+{
+ g_return_if_fail(rec != NULL);
+ g_return_if_fail(fname != NULL);
+
+ g_free_not_null(rec->fname);
+ rec->fname = g_strdup(fname);
+
+ if (create_mode != -1)
+ rec->create_mode = create_mode;
+}
diff --git a/src/lib-config/set.c b/src/lib-config/set.c
new file mode 100644
index 00000000..49457576
--- /dev/null
+++ b/src/lib-config/set.c
@@ -0,0 +1,122 @@
+/*
+ set.c : irssi configuration - change settings in memory
+
+ 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"
+
+void config_node_remove(CONFIG_NODE *parent, CONFIG_NODE *node)
+{
+ g_return_if_fail(parent != NULL);
+ g_return_if_fail(node != NULL);
+
+ parent->value = g_slist_remove(parent->value, node);
+
+ switch (node->type) {
+ case NODE_TYPE_KEY:
+ case NODE_TYPE_VALUE:
+ case NODE_TYPE_COMMENT:
+ g_free_not_null(node->value);
+ break;
+ case NODE_TYPE_BLOCK:
+ case NODE_TYPE_LIST:
+ while (node->value != NULL)
+ config_node_remove(node, ((GSList *) node->value)->data);
+ break;
+ }
+ g_free_not_null(node->key);
+ g_free(node);
+}
+
+void config_nodes_remove_all(CONFIG_REC *rec)
+{
+ g_return_if_fail(rec != NULL);
+
+ while (rec->mainnode->value != NULL)
+ config_node_remove(rec->mainnode, ((GSList *) rec->mainnode->value)->data);
+}
+
+
+void config_node_set_str(CONFIG_NODE *parent, const char *key, const char *value)
+{
+ CONFIG_NODE *node;
+ int no_key;
+
+ g_return_if_fail(parent != NULL);
+
+ no_key = key == NULL;
+ node = no_key ? NULL : config_node_find(parent, key);
+
+ if (value == NULL) {
+ /* remove the key */
+ if (node != NULL) config_node_remove(parent, node);
+ return;
+ }
+
+ if (node != NULL)
+ g_free(node->value);
+ else {
+ node = g_new0(CONFIG_NODE, 1);
+ parent->value = g_slist_append(parent->value, node);
+
+ node->type = no_key ? NODE_TYPE_VALUE : NODE_TYPE_KEY;
+ node->key = no_key ? NULL : g_strdup(key);
+ }
+
+ node->value = g_strdup(value);
+}
+
+void config_node_set_int(CONFIG_NODE *parent, const char *key, int value)
+{
+ char str[MAX_INT_STRLEN];
+
+ g_snprintf(str, sizeof(str), "%d", value);
+ return config_node_set_str(parent, key, str);
+}
+
+void config_node_set_bool(CONFIG_NODE *parent, const char *key, int value)
+{
+ return config_node_set_str(parent, key, value ? "yes" : "no");
+}
+
+int config_set_str(CONFIG_REC *rec, const char *section, const char *key, const char *value)
+{
+ CONFIG_NODE *parent;
+
+ g_return_val_if_fail(rec != NULL, -1);
+ g_return_val_if_fail(section != NULL, -1);
+
+ parent = config_node_traverse(rec, section, TRUE);
+ if (parent == NULL) return -1;
+
+ config_node_set_str(parent, key, value);
+ return 0;
+}
+
+int config_set_int(CONFIG_REC *rec, const char *section, const char *key, int value)
+{
+ char str[MAX_INT_STRLEN];
+
+ g_snprintf(str, sizeof(str), "%d", value);
+ return config_set_str(rec, section, key, str);
+}
+
+int config_set_bool(CONFIG_REC *rec, const char *section, const char *key, int value)
+{
+ return config_set_str(rec, section, key, value ? "yes" : "no");
+}
diff --git a/src/lib-config/write.c b/src/lib-config/write.c
new file mode 100644
index 00000000..30a41fd4
--- /dev/null
+++ b/src/lib-config/write.c
@@ -0,0 +1,336 @@
+/*
+ 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) {
+ 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 ((unsigned char) *text <= 32 || *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_str(rec, node->key) == -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_str(rec, node->key) == -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)
+{
+ 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);
+
+ 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;
+ if (config_write_block(rec, rec->mainnode, FALSE, TRUE) == -1) {
+ /* write error */
+ config_error(rec, errno == 0 ? "bug" : g_strerror(errno));
+ return -1;
+ }
+ write(rec->handle, "\n", 1);
+
+ close(rec->handle);
+ rec->handle = -1;
+
+ return 0;
+}