/*
// File: hypertext.c
// Purpose: a generic hypertext information storage device
// 93-07-25 Written by Douglas Reay (Pallando @ TMI-2)
// The name "hypertext" is used in the sense of non-linear text,
// a usage originating with Ted Nelson c1965.
// See /obj/tools/memopad.c for an example of use.
// 93-08-28 Default option added by Pallando on idea by Grendel
// 93-09-17 Pallando changed option r to use more()
// 93-09-18 Pallando added version control and settable name of default item
// 93-09-19 Pallando made adding new item types easier
*/
#define HYPERTEXT_VERSION 930919
#include <uid.h>
#include <config.h>
#include <mudlib.h>
#define TEMP_FILE temp_file( query( "name" ) )
inherit OBJECT;
mapping data;
mapping options;
int hypertext_version;
static string index;
static string *history;
varargs int get_option( string option );
// Security.
int query_prevent_shadow() { return 1; }
// The previous object (the user) may read item <index>
varargs int valid_read( string arg )
{
return 1;
}
// The previous object (the user) may alter item <index>
varargs int valid_write( string arg )
{
return ( geteuid( this_object() ) != ROOT_UID ) &&
( geteuid( this_object() ) != "NONAME" );
}
// The file a particular user's copy of this object should save to.
string datafile()
{
if( !previous_object() ) return TMP_DIR + query( "name" ) + "_noname";
return TMP_DIR + query( "name" ) + "_" + geteuid( previous_object() );
}
int do_save()
{
if( !valid_write() ) return 0;
save_object( datafile() );
return 1;
}
int remove()
{
do_save();
return ::remove();
}
void write_prompt()
{
printf( "(%s) [%s] ", index, implode( keys( options ), "," ) );
}
void increment_history()
{
if( query( "history" ) && ( index != history[0] ) )
history = ( ({ index }) + history )[0..( query( "history" ) )];
}
// **************** START OF OPTIONS ******************* //
// Option: delete
// Effect: deletes item (arg)
varargs int option_d( string arg )
{
// "sknil", you may notice, is "links" spelt backwards.
mapping item, sknil, links;
string *indexes, *names;
int loop;
if( !arg ) arg = index;
if( arg == query( "home" ) )
{
write( "You may not delete the home item.\n" );
return get_option();
}
if( !valid_write( arg ) )
{
write( "You may not alter item (" + arg + ")\n" );
return get_option();
}
if( !mapp( item = data[arg] ) )
{
write( "There is no item (" + arg + ")\n" );
return get_option();
}
if( arg == index ) index = query( "home" );
increment_history();
// Delete all links from this item.
if( mapp( links = item["links"] ) &&
( loop = sizeof( names = keys( links ) ) ) )
while( loop-- )
if( mapp( data[links[names[loop]]] ) &&
mapp( data[links[names[loop]]]["sknil"] ) )
map_delete( data[links[names[loop]]]["sknil"], arg );
// If other items have links to this item (the one to be deleted) then
// delete this item from their links.
if( mapp( sknil = item["sknil"] ) &&
( loop = sizeof( indexes = keys( sknil ) ) ) )
while( loop-- )
if( mapp( data[indexes[loop]] ) &&
mapp( data[indexes[loop]]["links"] ) )
map_delete( data[indexes[loop]]["links"],
sknil[indexes[loop]] );
map_delete( data, arg );
do_save();
write( "Ok.\n" );
return get_option();
}
// Option: edit
// Effect: create or alter an item
varargs int option_e( string arg )
{
string filename, tmp;
if( arg && ( sscanf( arg, "%s %s", tmp, filename ) == 2 ) )
{
if( !valid_write( tmp ) ) return get_option();
filename = resolv_path( "cwd", filename );
if( !file_exists( filename ) )
{
write( "There is no file called " + filename + "\n" );
return get_option();
}
index = tmp;
increment_history();
if( !mapp( data[index] ) ) data[index] = ([ ]);
data[index]["type"] = "file";
data[index]["data"] = filename;
data[index]["author"] = capitalize( geteuid( this_player() ) );
data[index]["time"] = time();
do_save();
write( "Ok, item (" + index + ") is " + filename + "\n" );
return get_option();
}
if( arg ) index = arg;
if( !valid_write( index ) ) return get_option();
increment_history();
write( "Edit item (" + index + ")\n" );
if( mapp( data[index] ) &&
stringp( data[index]["data"] ) &&
( data[index]["type"] == "text" ) )
write_file( TEMP_FILE, data[index]["data"] );
this_player()-> edit( TEMP_FILE, "finish", this_object() );
return 1;
}
// called by option_e if the body's edit() function is finished normally
void finish()
{
mapping item;
string text;
text = read_file( TEMP_FILE );
rm( TEMP_FILE );
if( text )
{
if( !mapp( data[index] ) ) data[index] = ([ ]);
data[index]["author"] = capitalize( geteuid( this_player() ) );
data[index]["time"] = time();
data[index]["data"] = text;
data[index]["type"] = "text";
write( "(" + index + ") changes saved.\n" );
} else {
write( "Changes not saved. Use \"d\" if wish to delete item.\n" );
}
do_save();
get_option();
}
// called by option_e if the body's edit() function is aborted
void abort()
{
rm( TEMP_FILE );
write( "Ok, edit abandoned.\n" );
get_option();
}
// Option: help
// Effect: provides help on the hyper object and its functions.
varargs int option_h( string arg )
{
if( arg )
{
if( query( "help/" + arg ) )
write( query( "help/" + arg ) );
else
write( wrap( "Help is available on: " +
implode( keys( query( "help" ) ), " " ) ) );
} else {
write( query( "help/options" ) );
}
return get_option();
}
// Option: index
// Effect: lists the indexes of all known items.
varargs int option_i( string arg )
{
string *indexes;
indexes = keys( data );
if( arg )
{
indexes = regexp( indexes, arg );
if( !sizeof( indexes ) )
{
write( "There are no indices matching the pattern " + arg + "\n" );
return get_option();
}
}
write( wrap( implode( indexes, " " ) ) );
return get_option();
}
// Option: unlink
// Effect: tries to remove a link {arg} from item (index)
// If arg2 is passed (as when called by option_l) it is silent
varargs int option_u( string arg, int arg2 )
{
mapping item, links;
string indx;
if( !arg )
{
write( "Syntax: u {name}\nEffect: unlinks the link {name}\n" );
return get_option();
}
if( !mapp( item = data[index] ) || !mapp( links = item["links"] ) )
{
write( "(" + index + ") has no links.\n" );
return get_option();
}
if( !( indx = links[arg] ) )
{
printf( "(%s) does not have a link {%s}\n", index, arg );
return get_option();
}
if( mapp( data[indx] ) && mapp( data[indx]["sknil"] ) )
map_delete( data[indx]["sknil"], index );
map_delete( data[index]["links"], arg );
if( arg2 ) return 1;
printf( "Link {%s} to Item (%s) removed.\n", arg, indx );
do_save();
return get_option();
}
// Option: link
// Effect: makes a link from item <index>
varargs int option_l( string arg )
{
int loop;
mapping item, links;
string indx, name, *names;
if( arg )
{
if( !valid_write( index ) ) return get_option();
if( sscanf( arg, "%s %s", name, indx ) != 2 )
{
write( "Syntax: l {name} (index)\n" );
return get_option();
}
if( !mapp( data[indx] ) )
{
write( "You need to create item (" + indx + ") first.\n" );
return get_option();
}
if( !mapp( data[index] ) ) data[index] = ([ ]);
if( mapp( data[index]["links"] ) )
{
if( data[index]["links"][name] )
option_u( name );
data[index]["links"][name] = indx;
} else {
data[index]["links"] = ([ name : indx ]);
}
if( mapp( data[indx]["sknil"] ) )
data[indx]["sknil"][index] = name;
else
data[indx]["sknil"] = ([ index : name]);
write( "Ok.\n" );
do_save();
return get_option();
}
if( !mapp( item = data[index] ) || !mapp( links = item["links"] ) ||
!( loop = sizeof( names = keys( links ) ) ) )
{
write( "Item " + index + " is not linked to any other items.\n" );
return get_option();
}
write( "Item (" + index + ") has the links:\n" );
printf( "%-15s\t%s\n", "{name}", "(index)" );
while( loop-- ) printf( "%-15s\t%s\n", names[loop], links[names[loop]] );
return get_option();
}
// Option: read
// Effect: displays item (arg)
varargs int option_r( string arg )
{
string indx;
mapping item;
mixed more_data;
if( !arg ) arg = index;
item = data[arg];
if( undefinedp( item ) )
{
write( "There is no item (" + arg + ")\n" );
return get_option();
}
if( !valid_read( arg ) )
{
write( "Yoy may not read (" + arg + ")\n" );
return get_option();
}
switch( item["type"] )
{
case "file":
{
write( "(" + arg + ") " + item["data"] + "\n" );
more_data = item["data"];
break;
}
case "text":
{
write( "(" + arg + ") by " + item["author"] +
" at " + ctime( item["time"] ) + "\n" );
more_data = explode( item["data"], "\n" );
break;
}
default:
{
if( !stringp( item["type"] ) )
write( "Error in item: no type.\n" );
else
if( !call_other( this_object(), "read_" + item["type"], arg ) )
write( "Unknown data type: " + item["type"] + "\n" );
return get_option();
}
}
indx = index;
index = arg;
if( (int)this_player()-> more( more_data ) )
{
return 1;
} else {
index = indx;
this_object()-> done_more();
return 0;
}
}
void done_more()
{
mapping item, links;
string *names;
item = data[index];
if( query( "show_links" ) && mapp( links = item["links"] ) &&
sizeof( names = keys( links ) ) )
{
write( wrap( "Links: " + implode( names, "," ) ) );
}
increment_history();
get_option();
}
// Option: previous
// Effect: lists previous items visited (ie a history function)
// if integer <arg> is specified, if goes back to that item
varargs int option_p( string arg )
{
int loop;
if( arg )
{
if( sscanf( arg, "%d", loop ) )
{
if( ( loop < sizeof( history ) ) && history[loop] )
return option_r( history[loop] );
else
write( "Your history doesn't go back that far.\n" );
} else write( arg + " is not a number.\n" );
} else write( "Item index history:\n" );
for( loop = 0 ; history[loop] ; loop++ )
printf( "%3d %s\n", loop, history[loop] );
return get_option();
}
// Option: quit
// Effect: only option not to do a return get_option()
varargs int option_q( string arg )
{
write( "Ok.\n" );
return 1;
}
// ****************** END OF OPTIONS ********************** //
varargs int get_option( string option )
{
string tmp, arg;
if( option == "" )
{
if( mapp( data[index] ) && mapp( data[index]["links"] ) &&
stringp( data[index]["links"]["default"] ) )
{
if( data[index]["links"]["default"] != index )
return option_r( data[index]["links"]["default"] );
} else if( ( index != query( "default" ) ) &&
mapp( data[query( "default" )] ) )
return option_r( query( "default" ) );
}
if( !option || option == "" )
{
write_prompt();
input_to( "get_option" );
return 1;
}
if( sscanf( option, "%s %s", tmp, arg ) ) option = tmp;
if( options[option] )
return (int)call_other( this_object(), options[option], arg );
if( arg )
{
write( "You may not specify \"" + option + "\" with an argument.\n" );
return get_option();
}
if( mapp( data[index] ) && mapp( data[index]["links"] ) &&
data[index]["links"][option] )
return option_r( data[index]["links"][option] );
return option_r( option );
}
int cmd_start( string arg )
{
if( stringp( arg ) && data[arg] )
return option_r( arg );
return option_r( query( "home" ) );
}
void init()
{
add_action( "cmd_start", query( "start" ) );
}
void set_up()
{
options = ([
"d" : "option_d",
"e" : "option_e",
"h" : "option_h",
"i" : "option_i",
"l" : "option_l",
"p" : "option_p",
"q" : "option_q",
"r" : "option_r",
"u" : "option_u",
]);
set( "help/options", @EndText
HELP PAGE HELP PAGE
Help on interactive hypertext options
This object contains a collection of text items, either as variables
in the object or as references to files in the mudlib.
Each item has an index - shown in () - a string with no spaces
such as "longsword", "12.4", "item1", "driver/bugs" or whatever
naming scheme you chose.
Items may have links - shown in {} - to other items.
Option:
EndText
+ @EndText
l - list the links from the current item.
l {name} (index) - links the current item to item (index)
typing "{name}" at the prompt will take you to the linked item.
(index) - displays item (index).
r (index) - displays item (index). Typing "r" will display the current item.
e (index) - edit item (index). Typing "e" will edit the current item.
e (index) <filename> - set item (index) to refer to the file <filename>
h - help (this message)
q - quit
i <pattern> - show all items who indexes match <pattern>
p - a history command. display the indexes of your previous items.
p <n> - go to the <n>'th item in the history list.
d (index) - deletes item (index)
u {name} - deletes the link {name}
EndText
);
set( "id", ({ "hypertext", "hypereader" }) );
set( "name", "hypereader" );
set( "home", "start" );
set( "start", "hyper" );
set( "default", "default" );
set( "history", 20 );
hypertext_version = HYPERTEXT_VERSION;
}
void check_version()
{
string *indexes;
int loop;
if( hypertext_version == HYPERTEXT_VERSION ) return;
if( stringp( hypertext_version ) )
sscanf( hypertext_version, "%d", hypertext_version );
if( hypertext_version < 930918 )
{
set( "default", "default" );
}
if( hypertext_version < 930919 )
{
loop = sizeof( indexes = keys( data ) );
while( loop-- )
{
if( undefinedp( data[indexes[loop]]["filename"] ) )
{
data[indexes[loop]]["type"] = "text";
data[indexes[loop]]["data"] = data[indexes[loop]]["text"];
map_delete( data[indexes[loop]], "text" );
} else {
data[indexes[loop]]["type"] = "file";
data[indexes[loop]]["data"] = data[indexes[loop]]["filename"];
map_delete( data[indexes[loop]], "filename" );
}
}
}
hypertext_version = HYPERTEXT_VERSION;
do_save();
}
// It is up to the inheriting object to ensure that a wizard can't clone this
// if they shouldn't have write access to the datafile
void create()
{
data = ([ ]);
set_up();
if( !geteuid( this_object() ) )
seteuid( ROOT_UID );
if( !geteuid( this_object() ) )
seteuid( getuid() );
restore_object( datafile() );
if( query( "history" ) )
history = allocate( query( "history" ) + 2 );
check_version();
}