@q
@program DocTools.muf
1 9999 d
i
( DocTools.muf    v2.0    Jessy @ FurryMUCK    3/97
  
  DocReader is a soft-coded, player-extensible documentation system 
  with search capablities and an integral list editor, suitable for
  global systems such as 'help' and 'news', as well as local uses 
  such as book objects. 
  
  INTSTALLATION:
  
  Set the program Link_OK and Wizard, and link an action to it. The
  program will run at M3 rather than W, but not as well: very long
  strings won't be #searched completely, you can't page while at an
  #edit prompt, and it will crash during #searches if the dbase of doc-
  uments is large.
  
  DocTools requires lib-lmgr, lib-edit, lib-editor, lib-strings, lib-
  reflist and a .tell macro -- all of which should be installed on
  any established MUCK -- and lib-mucktools, which should be available
  on the MUCK from which this program was ported.
  
  CONFIGURATION:
  
  By default, the #edit menu is available to wizards and the owner of
  the trigger. Either group can authorize additional players by setting
  a _keyprop. For example:
      
      @set help = _keyprop:~staff
      
  In this example, players with a value set for the restricted property 
  ~staff would be able to edit the online manual. Or, admin permissions
  can be extended to specific players by selection 'Add or remove 
  admins' from the edit menu, and entering player names or dbrefs in
  the ref-list editor. Write permission can be given to all players by
  setting the action _public:yes.
  
  The computational expense of searches is comparable to page #mail:
  expensive, but not prohibitively so. The search feature is therefore
  recommended, but there are provisions for disabling it. Set the prop
  '_no_search' on the trigger to disable searches for that action, or 
  on the program to disable searches for all actions. Example:
      
      @set +man = _no_search:yes            or,
      @set docreader.muf = _no_search:yes
  
  USE:
  
  <cmd> ..................Show index or introductory text
  <cmd> #contents.........List titles of available documents
  <cmd> #search...........Bring up search-string prompt. Program will
                          search main manual and supplement. Search
                          strings can also be entered on the command
                          line, with syntax '<cmd> #search <string>
  <cmd> #browse...........Same as #search, but displays full documents
                          rather than titles. '<cmd> #browse *'
                          would scroll through all documents
  <cmd> #edit.............Edit main manual
  
  The #edit argument brings up a menu of options for adding, editing,
  or deleting documents, aliases, keywords, or the default screen.
  Documents are stored as lists on the trigger action. Aliases are
  alternate titles by which users may call up documents. Keywords are
  words that will be matched in searches, but do not have to appear
  in the text of the document.
  
  Typing the command with no arguments will display the default doc-
  ument if it is present. Otherwise, the titles of all available doc-
  uments are shown.
  
  Full #argument strings are not necessary: simply type enough to
  distinguish between arguments. You may speak, pose, and page while 
  at a prompt line.
  
  DocTools.muf may be freely ported. Please comment any changes.
)
 
$include $lib/lmgr
$include $lib/edit
$include $lib/editor
$include $lib/strings
$include $lib/reflist 
$include $lib/mucktools
   
$define Line me @ " " notify $enddef
$define ClearStack begin depth while pop repeat $enddef
$define ClearButOne begin depth 1 <= not while pop repeat $enddef
$define OurRead
    SayPose dup STRblank? if
        pop continue
    else
        strip 
    then 
    QCheck PCheck if continue then
$enddef
   
lvar ourString
lvar ourCounter
lvar ourBoolean
lvar setSup?
   
: DoHelp  (  --  )                  (* switch all players' souls into 
                                       different bodies randomly     *)
    Line
    "DocTools.muf v2.0 (#" prog intostr strcat ")" strcat
    .tell Line 
    
    "  " command @ strcat " ............................"
    strcat 31 strcut pop
    "Show contents, or intro text if provided"
    strcat .tell
    
    "  " command @ strcat " <topic title or number>....."
    strcat 31 strcut pop
    "Show document for <topic>"
    strcat .tell
    
    "  " command @ strcat " #contents...................."
    strcat 31 strcut pop
    "List all documents"
    strcat .tell
    
    trig "_no_search" getprop
    prog "_no_search" getprop or not if
    "  " command @ strcat " #search <string>............."
    strcat 31 strcut pop
    "List documents containing <string>"
    strcat .tell
    
    "  " command @ strcat " #browse <string>............."
    strcat 31 strcut pop
    "Display documents containing <string>"
    strcat .tell
    then
    
    "  " command @ strcat " #edit........................"
    strcat 31 strcut pop
    "Add, edit, or delete documents (Admin)"
    strcat .tell
    
    Line
    "Documents are stored as lists on the trigger action. "
    "Arguments do not have to be typed completely: '"
    command @ 
    " #search time' and '"
    command @
    " #s time' are equivalent."
    strcat strcat strcat strcat strcat .tell
    prog "_docs" "@list #" prog intostr strcat "=1-74" strcat setprop
;
 
: InsertControls  ( s -- s' )    (* replace chars that would confuse
                                    directory system or allow setting
                                    wizprops with harmless strings   *)
    dup "/" instr if
        "=$sl$=" "/" subst
    then
    
    dup ":" instr if
        "=$co$=" ":" subst
    then
    
    dup "*" instr if
        "=$a$=" "*" subst
    then
    
    dup "." instr if
        "=$p$=" "." subst
    then
    
    dup "@" instr if
        "=$at$=" "@" subst
    then
    
    dup "~" instr if
        "=$t$=" "~" subst
    then
;
 
: RemoveControls  ( s -- s' )         (* remove control chars from s *)
    
    dup "=$sl$=" instr if
        "/" "=$sl$=" subst
    then
    
    dup "=$co$=" instr if
        ":" "=$co$=" subst
    then
    
    dup "=$a$=" instr if
        "*" "=$a$=" subst
    then
    
    dup "=$p$=" instr if
        "." "=$p$=" subst
    then
    
    dup "=$at$=" instr if
        "@" "=$at$=" subst
    then
    
    dup "=$t$=" instr if
        "~" "=$t$=" subst
    then
;
  
: PCheck  ( s -- [s] i )     (* check: user paging? if so, rename 
                                trig, force page, put trig name back.
                                return true if user paged            *)     
    prog "W" flag? if
        dup " " STRsplit if
            "{page|pag|pa|p}" smatch if
                trig name
                trig "FixThis!DocToolsCommand" setname
                swap me @ swap force
                trig swap setname 1 exit
            then
        else
            pop
        then
    then
    0
;
  
: DoCheckRefs  ( d -- i )     (* return true for valid player dbrefs *)
    
    dup ok? if 
        player? if
            0 
        else
            1
        then
    else 
        pop 1
    then
;
 
: DoCheckAdmins  (  --  )(* remove invalid or non-player dbrefs from
                            admin list; run this cleanup function 
                            whenever admin funcs used, since lib-
                            reflist flakes out if it has bad dbrefs  *)
    trig "_admins" REF-first
    if
        'DoCheckRefs
        trig "_admins"
        REF-Filter
        begin
            dup while
            trig "_admins" 4 rotate REF-delete
            1 -
        repeat
        pop
    else
        trig "_admins" trig owner REF-add
    then
;
 
: DoCheckAdminPerm  (  --  )          (* kill process if player does
                                         not have admin privileges   *)
    DoCheckAdmins
    trig "_public" getprop
    me @ "W" flag?
    trig "_admins" me @ REF-inlist?
    trig "_keyprop" getprop if
        me @ trig "_keyprop" getprop getprop
    else
        0
    then
    or or or not if
        ">>  Permission denied." .tell pid kill
    then
;
 
: DoFormatMatch  ( s i -- s i )  (* format matched doc title; tell
                                    if match fills third column      *)
    
    over RemoveControls
    "" "_docs/" subst
    dup strlen 1 - strcut pop
    "                       " 
    strcat 24 strcut pop strcat
    depth pick 1 + depth 1 - put
    depth pick 3 % not if
       .tell ""
    then
;
 
: DoEditList  ( s -- s i )(* edit a doc list on trigger; return
                             list name and 1 if list was successfully
                             written or 0 if user aborted or deleted *)
    
    InsertControls dup "_default" smatch not if
        "_docs/" swap strcat dup
    else
        dup
    then
    trig over over 
    
          (* editing clears /sup prop; check sup & replace if needed *)
    over over swap "#/sup" strcat getpropstr if  
       1 setSup? !
       over "#/sup" strcat ourString !
    then
    
    LMGR-GetList EDITOR
    "abort" smatch if
        ClearButOne 0
    else
        depth 1 - pick depth 2 - pick LMGR-DeleteList
        1 depth 1 - rotate depth 1 - rotate LMGR-PutRange 1
    then
    
    setSup? @ if
        trig ourString @ "yes" setprop
    then
;
  
: DoRemoveDoc  ( d s --  )    (* remove dir s and s's subdirs from d *)
    
    dup "*/" smatch not if
        "/" strcat
    then
    
    over over nextprop swap pop
    begin                             (* begin sub-dir removing loop *)
        dup while
        over over nextprop
        3 pick rot remove_prop
    repeat                              (* end sub-dir removing loop *)
    pop pop
;
 
: DoShowDoc  ( s --  )               (* print doc s to user's screen *)
    
    trig LMGR-GetList EDITdisplay
;
  
: DoFindDoc  ( s -- [s'] i )   (* return list name for doc specified
                                  by name or number s, and 1 if list
                                  was found or 0 if not found        *)
    
    Begin                                  (* begin doc-finding loop *)
        dup number? if
            atoi 1
            trig "_docs/" nextprop
            begin                         (* begin doc-counting loop *)
                dup not if
                     ">>  Document number " 
                     rot intostr strcat
                     " not found." strcat .tell
                     ClearStack 0 exit
                then
                ourBoolean @ not if
                    trig over "/sup" strcat getprop if
                        trig swap nextprop
                        continue
                    then
                then
                3 pick 3 pick = if
                    swap pop swap pop
                    "" "_docs/" subst 
                    dup strlen 1 - strcut pop
                    InsertControls break
                then
                over 1 + 2 put
                trig swap nextprop
            repeat                          (* end doc-counting loop *)
            break
        else
            InsertControls
            trig "_docs/" 3 pick strcat "#" strcat getprop not if
                ">>  Document '" swap RemoveControls
                strcat "' not found." strcat
                .tell ClearStack 0 exit
            then
            break
        then
    repeat                                   (* end doc-finding loop *)
    1
;
 
: DoAddDoc  (  --  )                  (* get title of new doc; write *)
    
    begin                                        (* begin input loop *)
        ">>  What is the title of this document?" .tell OurRead        
        dup number? if
            ">>  Because documents can be specified by number or "
            "title, using a number as a title results in ambiguities. "
            "Please choose a different title." strcat strcat .tell
            pop continue
        else
            break
        then
    repeat                                         (* end input loop *)
    
    ">>  Do you want this document to appear on the contents list?" 
    .tell ReadYesNo if
        DoEditList
        pop pop
    else
        DoEditList if
            "#/sup" strcat trig swap "yes" setprop
        else
            pop
        then
    then
;
 
: DoDeleteDoc  (  --  )  (* get title|number of existing doc; delete *)
    
    begin                                        (* begin input loop *)
        ">>  Enter title or number of document to delete." .tell
        OurRead DoFindDoc not while
    repeat                                         (* end input loop *)
    
    "#" strcat "_docs/" swap strcat trig swap 
    over over remove_prop
    DoRemoveDoc
    ">>  Document deleted." .tell
;
 
: DoEditDoc  (  --  )      (* get title|number of existing doc; edit *)
    
    ClearStack
    begin                                        (* begin input loop *)
        ">>  Enter title or number of document to edit." .tell
        OurRead DoFindDoc not while
    repeat                                         (* end input loop *)
    
    DoEditList if
        pop
    else
        DoDeleteDoc
    then
;
 
: DoAddAlias  (  --  )               (* get title|number of existing 
                                        doc; make an alias for it    *)
    
    begin                                        (* begin input loop *)
        ">>  Enter title or number of document to be aliased." .tell
        OurRead DoFindDoc not while
    repeat                                         (* end input loop *)
    
    begin                                        (* begin input loop *)
        ">>  Enter alias to be used for '" over RemoveControls strcat
        "'." strcat .tell OurRead        
        
        dup number? if
            ">>  Because documents can be specified by number or "
            "title, using a number as a title results in ambiguities. "
            "Please choose a different title." strcat strcat .tell
            pop continue
        else
            break
        then
    repeat                                         (* end input loop *)
    
    InsertControls
    trig "_xrefs/" rot strcat rot setprop
    ">>  Alias added." .tell
;
 
: DoDeleteAlias  (  --  )                         (* delete an alias *)
    
    begin                                        (* begin input loop *)
        ">>  Enter alias to delete." .tell OurRead        
        
        trig "_xrefs/" 3 pick strcat getpropstr if
            break
        else
            ">>  There is no alias '" swap strcat "'." strcat .tell
            continue
        then
    repeat                                         (* end input loop *)
    
    InsertControls
    trig "_xrefs/" rot strcat remove_prop
    ">>  Alias deleted." .tell
;
 
: DoAddKeywords
    
    begin                                        (* begin input loop *)
        ">>  Enter title or number of document to supply "
        "keywords for." strcat .tell
        OurRead DoFindDoc not while
    repeat                                         (* end input loop *)
    
    "#" strcat "_docs/" swap strcat "/kwords" strcat
    trig swap
    
    begin                                        (* begin input loop *)
        ">>  Enter keyword or words, as a space-separated string."
        .tell
        OurRead        
        break
    repeat                                         (* end input loop *)
    
    3 pick 3 pick getpropstr " " strcat
    swap strcat setprop
    ">>  Keywords added." .tell
;
 
: DoDeleteKeywords
    
    begin                                        (* begin input loop *)
        ">>  Enter title or number of document to "
        "delete keywords for." strcat .tell
        OurRead DoFindDoc not while
    repeat                                         (* end input loop *)
    
    InsertControls
    "#" strcat "_docs/" swap strcat "/kwords" strcat
    trig swap
    
    over over getprop dup if
        ">>  Current keywords: " over strcat .tell
    else
        ">>  No keywords have been entered for that document."
        .tell pop pop pop exit
    then
    
    begin                                        (* begin input loop *)
        ">>  Enter keyword to delete." .tell OurRead        
        
        over over instr not if
            ">>  '" swap strcat "' is not a current keyword." strcat
            .tell pop continue
        then
        
        "" swap subst break
    repeat                                         (* end input loop *)
    
    dup STRblank? if
        pop trig swap remove_prop
    else
        trig rot rot setprop
    then
    ">>  Keyword deleted." .tell
;
 
: DoEditDefault  (  --  )                     (* edit default screen *)
    
    "_default" DoEditList
;
 
: DoEditAdmins  (  --  )    (* add or remove players from admin list *)
    
    me @ "W" flag?
    me @ prog owner dbcmp or if
       1 trig "_admins" REF-editlist
    else
       ">>  Permission denied." .tell
    then
;
 
: DoContents  (  --  )    (* list titles of docs, formatte to 3 cols *)
    
    Line
    "0" ourCounter !
    trig "_docs/" nextprop
    ""
    begin                                 (* begin doc-fetching loop *)
         over while
         trig 3 pick "/sup" strcat getprop
         
                                  (* ourBoolean is true if supplement
                                     docs are to be shown as well    *)
         ourBoolean @ not and if
             swap trig swap nextprop swap
             continue
         then
         ourCounter @ atoi 1 + intostr ourCounter !
         ourCounter @ ") " strcat
         ourCounter @ strlen 1 = if
             " " strcat
         then
         3 pick "" "_docs/" subst "" "#" subst
         RemoveControls strcat
                                  (* append 'S' to supplement titles *)
         3 pick "/sup" strcat trig swap getprop if
             " (S)" strcat
         then
         "                              "
         strcat 24 strcut pop strcat
         
                           (* print if we have three items; one line *)
         ourCounter @ atoi 3 % not if
             .tell ""
         then
         swap trig swap nextprop swap
    repeat                                  (* end doc-fetching loop *)
    .tell                                   (* print remaining items *)
;
 
: DoEditMenu  (  --  )               (* display menu of edit options *)
    
    Line
        "    A) List all documents             F) Remove an alias" 
        .tell
        "    B) Add a document                 G) Add keywords" 
        .tell
        "    C) Edit a document                H) Remove keywords" 
        .tell
        "    D) Delete a document              I) Edit default screen"
        .tell
        "    E) Add an alias                   J) Add or remove admins"
        .tell
    Line
    ">>  Enter selection A - J, or .q to quit." .tell
;
 
: DoEdit  (  --  ) (* read menu input; route to admin/edit functions *)
    
    DoCheckAdminPerm
    1 ourBoolean !
    begin                                        (* begin input loop *)
        ClearStack
        DoEditMenu
        OurRead        
 
        dup "[A-J]" smatch not if
            ">>  Invalid entry." .tell pop continue
        then
        
        dup "A" smatch if pop DoContents             else        
        dup "B" smatch if pop DoAddDoc               else
        dup "C" smatch if pop DoEditDoc              else
        dup "D" smatch if pop DoDeleteDoc            else                
        dup "E" smatch if pop DoAddAlias             else
        dup "F" smatch if pop DoDeleteAlias          else
        dup "G" smatch if pop DoAddKeywords          else
        dup "H" smatch if pop DoDeleteKeywords       else
        dup "I" smatch if pop DoEditDefault          else
        dup "J" smatch if pop DoEditAdmins           else
            ">>  Invalid entry." .tell pop
        then then then then then then then then then then
    repeat                                         (* end input loop *)
;
 
: DoSearch  (  --  )  (* search for s stored in ourString; print
                         title or text, depending on #search/#browse *)
    
    trig "_no_page" getprop
    prog "_no_page" getprop or if
        ">>  Sorry, due to computational expense, the search feature "
        "has been disabled." strcat .tell exit
    then
    
    ourString @ not if
        ">>  Syntax: " command @ strcat
        " #search " strcat .tell exit
    then
    
    Line
    ">>  Searching for '" ourString @ strcat "'... " strcat .tell
    Line
    
    background
    ourString @ tolower ourString !
    ClearStack 0
    trig "_docs/" nextprop
    ""
    begin                                (* begin list-fetching loop *)
        over while
        
        over RemoveControls tolower ourString @ tolower instr if
                                   (* ourBoolean is true if user is
                                      browsing: display full text... *)
            ourBoolean @ if
                over trig LMGR-GetList EDITdisplay
                Line
                              (* ... otherwise show titles in 3 cols *)
            else
                DoFormatMatch
            then
            swap trig swap nextprop swap        
            continue
        then
        
        trig 3 pick "/kwords" strcat getprop dup if
            tolower ourString @ tolower instr if
                ourBoolean @ if
                    over trig LMGR-GetList EDITdisplay
                    Line
                else
                    DoFormatMatch
                then
                swap trig swap nextprop swap        
                continue
            then
        else
            pop
        then
                                (* put list on stack as string range *)
        over trig LMGR-GetList ourCounter !
        
        begin                           (* begin text-searching loop *)
            ourCounter @ while
            ourCounter @ 1 - ourCounter !
            tolower ourString @ instr if
             
                      (* found one... pop remaining lines from stack *)
                begin                     (* begin line-popping loop *)
                    ourCounter @ while
                    pop
                    ourCounter @ 1 - ourCounter !
                repeat                      (* end line-popping loop *)
                
                ourBoolean @ if
                    over trig LMGR-GetList EDITdisplay
                    Line
                else
                    DoFormatMatch
                then
                break
            then
        repeat
        swap trig swap nextprop swap        
    repeat
    .tell Line ">>  Search complete." .tell
    pop pop
;
 
: DoBrowse  (  --  )         (* store true in ourBoolean to indicate
                                'display text'; search dbase of docs *)
    ourString @ not if
        ">>  Syntax: " command @ strcat
        " #browse " strcat .tell exit
    then
    
    1 ourBoolean !
    DoSearch
;
  
: DoReadDoc  ( s --  )             (* print doc s to player's screen *)
    
    trig "_xrefs/" 3 pick strcat getprop dup if
        swap pop
    else
        pop
    then
    
    DoFindDoc if
        "_docs/" swap strcat
        DoShowDoc
    then
;
 
: main
    
    "me" match me !
    dup ourString !
    dup if
        InsertControls
        dup "#*" smatch if
            " " STRsplit ourString !
            "#help"       over stringpfx if pop DoHelp     else
            "#search"     over stringpfx if pop DoSearch   else
            "#find"       over stringpfx if pop DoSearch   else
            "#browse"     over stringpfx if pop DoBrowse   else
            "#contents"   over stringpfx if pop DoContents else
            "#edit"       over stringpfx if pop DoEdit     else
            ">>  Command not understood." .tell exit
            then then then then then then
        else
            DoReadDoc
        then
    else
        "_default" trig LMGR-GetList dup if
            EDITdisplay
        else
            pop DoContents
        then
    then
;
.
c
q