From 53b248f6deafb553aae133792659a8412bc00925 Mon Sep 17 00:00:00 2001 From: Timo Sirainen Date: Fri, 14 Apr 2000 11:27:14 +0000 Subject: [PATCH] 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 - 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 [ON/OFF] - same as /SET TOGGLE git-svn-id: http://svn.irssi.org/repos/irssi/trunk@163 dbcabf3a-b0e7-0310-adc4-f8d773084564 --- NEWS | 45 +++++ README | 1 - TODO | 38 ++++ config | 138 +++++++------- configure.in | 27 --- docs/COMMANDS | 22 ++- docs/FORMATS | 9 +- docs/PERL | 3 +- docs/SIGNALS | 2 +- docs/SPECIAL_VARS | 104 +++++++++++ irssi.spec.in | 54 +++++- src/Makefile.am | 2 +- src/common-setup.h | 60 +----- src/common.h | 10 +- src/lib-config/Makefile.am | 12 +- src/lib-config/get.c | 256 ++++++++++++++++++++++++++ src/lib-config/iconfig.h | 136 ++++++++++++++ src/lib-config/irssi-config.c | 189 ------------------- src/lib-config/irssi-config.h | 25 --- src/lib-config/module.h | 6 + src/lib-config/parse.c | 335 +++++++++++++++++++++++++++++++++ src/lib-config/set.c | 122 ++++++++++++ src/lib-config/write.c | 336 ++++++++++++++++++++++++++++++++++ 23 files changed, 1532 insertions(+), 400 deletions(-) create mode 100644 docs/SPECIAL_VARS create mode 100644 src/lib-config/get.c create mode 100644 src/lib-config/iconfig.h delete mode 100644 src/lib-config/irssi-config.c delete mode 100644 src/lib-config/irssi-config.h create mode 100644 src/lib-config/module.h create mode 100644 src/lib-config/parse.c create mode 100644 src/lib-config/set.c create mode 100644 src/lib-config/write.c 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 + + * 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 - 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 [ON/OFF] - same as /SET TOGGLE + v0.7.28 2000-03-11 Timo Sirainen + 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 = "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 = "";command = "Op";}, - {label = "Op";command = "/op %s";}, - {label = "Deop";command = "/deop %s";}, - {label = "Voice";command = "/voice %s";}, - {label = "Devoice";command = "/devoice %s";}, - {label = "Kick";command = "/kick %s %s";}, - {label = "Ban";command = "/ban %s";}, - {label = "Kick+ban";command = "/kickban %s %s";}, - {label = "Knockout";command = "/knockout %s %s";}, - {label = "";command = "";}, - {label = "";command = "CTCP";}, - {label = Ping;command = "/ping %s";}, - {label = Version;command = "/ver %s";}, - {label = "";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 = "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 = ""; command = "Op";}, + {label = "Op"; command = "/op %s";}, + {label = "Deop"; command = "/deop %s";}, + {label = "Voice"; command = "/voice %s";}, + {label = "Devoice"; command = "/devoice %s";}, + {label = "Kick"; command = "/kick %s %s";}, + {label = "Ban"; command = "/ban %s";}, + {label = "Kick+ban"; command = "/kickban %s %s";}, + {label = "Knockout"; command = "/knockout %s %s";}, + {label = ""; command = "";}, + {label = ""; command = "CTCP";}, + {label = Ping; command = "/ping %s";}, + {label = Version; command = "/ver %s";}, + {label = ""; 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 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...] ** 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. + +TOGGLE key [ON|OFF] + + Same as /SET TOGGLE, or if ON or OFF parameter is given + it will work just like /SET. + +SAVE + + Save configuration to disk. ALIAS, UNALIAS [command] Set/remove alias, /unalias is the same as /alias without command - - 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 - Typing extra / before /command (//command) ignores any aliases IGNORE, UNIGNORE [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 @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 - 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 [ON/OFF] - same as /SET 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 #include #include -#include -#include #ifdef HAVE_UNISTD_H # include @@ -38,10 +36,14 @@ #include #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 - -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; +}