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; +}