steam-shell._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2004 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  * Copyright (C) 2003-2004 Martin Baehr
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17  *
18  * $Id: debug.pike.in,v 1.1 2008/03/31 13:39:57 exodusd Exp $
19  */
20 inherit "applauncher.pike";
21  inherit Tools.Hilfe;
22  inherit Tools.Hilfe.Evaluator;
23  inherit Tools.Hilfe;
24 class steam-shell : public applauncher.pike{
25 public:
26 
27 
28 
29 #define OBJ(o) _Server->get_module("filepath:tree")->path_to_object(o)
30 
31 Stdio.Readline readln;
32 mapping options;
33 int flag=1,c=1;
34 string pw,str;
35 object me;
36 
37 protected class StashHelp {
38  string help(string what) { return "Show STASH help"; }
39 
40  void exec(Evaluator e, string line, array words,
41  array tokens) {
42  line = words[1..]*" ";
43  function(array|string, mixed ... : void) write = e->safe_write;
44 
45  constant all = #"
46 list List Gates/Exits, Documents, Containers in the current Room.
47 goto Goto a Room using a full path to the Room.
48 title Set your own description.
49 room Describe the Room you are currently in.
50 look Look around the Room.
51 take Copy a object in your inventory.
52 gothrough Go through a gate.
53 create Create an object (File/Container/Exit) in current Room.
54 peek Peek through a container.
55 inventory(i) List your inventory.
56 edit Edit a file in the current Room.
57 hilfe Help for Hilfe commands.
58 ";
59  switch(line) {
60 
61  case "commands":
62  write(all);
63  return;
64 
65  case "list":
66  write("List Gates/Exits, Documents, Containers in the current Room.\n");
67  return;
68  case "goto":
69  write("Goto a Room using a full path to the Room.\n");
70  return;
71  case "title":
72  write("Set your own description.\n");
73  return;
74  case "room":
75  write("Describe the Room you are currently in.\n");
76  return;
77  case "look":
78  write("Look around the Room.\n");
79  return;
80  case "take":
81  write("Copy a object in your inventory.\n");
82  return;
83  case "gothrough":
84  write("Go through a gate.\n");
85  return;
86  case "create":
87  write("Create an object (File/Container/Exit) in current Room.\n");
88  return;
89  case "peek":
90  write("Peek through a container.\n");
91  return;
92  case "i":
93  case "inventory":
94  write("Lists your inventory\n");
95  return;
96  case "edit":
97  write("Edit a file in the current Room.\n");
98  return;
99  //Hilfe internal help
100  case "me more":
101  write( documentation_help_me_more );
102  write("Type \"hilfe\" to get more help on Hilfe commands\n");
103  return;
104  case "hilfe todo":
105  write(hilfe_todo);
106  return;
107  case "about hilfe":
108  e->print_version();
109  write(cvs_version+#"
110 Initial version written by Fredrik Hübinette 1996-2000
111 Rewritten by Martin Nilsson 2002
112 ");
113  return;
114  default:
115  write(stash_help_doc);
116  write(all);
117  write("\n\nEnter \"help me more\" for further Hilfe help.\n\n");
118  }
119  }
120 }
121 
122 class Handler
123 {
124 public:
125 
126  object p;
127  void create(mapping _constants)
128  {
129  readln = Stdio.Readline();
130  p = ((program)"tab_completion.pmod")();
131  readln = p->readln;
132  write=predef::write;
133  ::create();
134  p->load_hilferc();
135  p->constants+=_constants; //For listing sTeam commands and objects on tab
136  constants = p->constants; //For running those commands
137  readln->get_input_controller()->bind("\t",p->handle_completions);
138  commands->help = StashHelp();
139  commands->hilfe = CommandHelp();
140  }
141 
142  void add_constants(mapping a)
143  {
144  constants = constants + a;
145  }
146 /* void add_variables(mapping a)
147  {
148  variables = variables + a;
149  }*/
150 }
151 
152 object _Server,users;
153 mapping all;
154 string path="/";
155 Stdio.Readline.History readline_history;
156 
157 void ping()
158 {
159  call_out(ping, 10);
160  mixed a = conn->send_command(14, 0);
161  if(a=="sTeam connection lost.")
162  {
163  flag = 0;
164  readln->set_prompt(getpath()+"~ ");
165  conn = ((program)"client_base.pike")();
166  conn->close();
167  if(conn->connect_server(options->host, options->port))
168  {
169  remove_call_out(ping);
170  ping();
171  if(str=conn->login(options->user, pw, 1))
172  {
173  _Server=conn->SteamObj(0);
174  users=_Server->get_module("users");
175  me = users->lookup(options->user);
176  handler->add_constants(assign(conn,_Server,users));
177  flag=1;
178  readln->set_prompt(getpath()+"> ");
179  }
180  }
181  }
182 }
183 
184 object handler, conn;
185 mapping myarray;
186 int main(int argc, array argv)
187 {
188  options=init(argv);
189  _Server=conn->SteamObj(0);
190  users=_Server->get_module("users");
191  me = users->lookup(options->user);
192  all = assign(conn,_Server,users);
193  all = all + (([
194  ]));
195  handler = Handler(all);
196  array history=(Stdio.read_file(options->historyfile)||"")/"\n";
197  if(history[-1]!="")
198  history+=({""});
199 
200  readline_history=Stdio.Readline.History(512, history);
201 
202  readln->enable_history(readline_history);
203 
204  handler->add_input_line("start backend");
205 
206  string command;
207  myarray = ([
208  "list" : list,
209  "goto" : goto_room,
210  "title" : set_title,
211  "room" : desc_room,
212  "look" : look,
213  "take" : take,
214  "gothrough" : gothrough,
215  "create" : create_ob,
216  "peek" : peek,
217  "inventory" : inventory,
218  "i" : inventory,
219  "edit" : editfile,
220  ]);
221 // Regexp.SimpleRegexp a = Regexp.SimpleRegexp("[a-zA-Z]* [\"|'][a-zA-Z _-]*[\"|']");
222  array command_arr;
223  while((command=readln->read(
224  sprintf("%s", (handler->state->finishedp()?getstring(1):getstring(2))))))
225  {
226  if(sizeof(command))
227  {
228  Stdio.write_file(options->historyfile, readln->get_history()->encode());
229  command = String.trim_whites(command);
230 // if(a->match(command))
231 // command_arr = array_sscanf(command,"%s [\"|']%s[\"|']");
232 // else
233  command_arr = command/" ";
234  if(myarray[command_arr[0]])
235  {
236  int num = sizeof(command_arr);
237  mixed result = catch {
238  if(num==2)
239  myarray[command_arr[0]](command_arr[1]);
240  else if(num==3)
241  myarray[command_arr[0]](command_arr[1],command_arr[2]);
242  else if(num==1)
243  myarray[command_arr[0]]();
244  };
245 
246  if(result!=0)
247  {
248  write("Wrong command.||maybe some bug.\n");
249  }
250  }
251  else
252  handler->add_input_line(command);
253 // array hist = handler->history->status()/"\n";
254 // if(hist)
255 // if(search(hist[sizeof(hist)-3],"sTeam connection lost.")!=-1){
256 // handler->write("came in here\n");
257 // flag=0;
258 // }
259  handler->p->set(handler->variables);
260  continue;
261  }
262 // else { continue; }
263  }
264  handler->add_input_line("exit");
265 }
266 
267 mapping init(array argv)
268 {
269  mapping options = ([ "file":"/etc/shadow" ]);
270 
271  array opt=Getopt.find_all_options(argv,aggregate(
272  ({"file",Getopt.HAS_ARG,({"-f","--file"})}),
273  ({"host",Getopt.HAS_ARG,({"-h","--host"})}),
274  ({"user",Getopt.HAS_ARG,({"-u","--user"})}),
275  ({"port",Getopt.HAS_ARG,({"-p","--port"})}),
276  ));
277 
278  options->historyfile=getenv("HOME")+"/.steam_history";
279 
280  foreach(opt, array option)
281  {
282  options[option[0]]=option[1];
283  }
284  if(!options->host)
285  options->host="127.0.0.1";
286  if(!options->user)
287  options->user="root";
288  if(!options->port)
289  options->port=1900;
290  else
291  options->port=(int)options->port;
292 
293  string server_path = "/usr/local/lib/steam";
294 
295  master()->add_include_path(server_path+"/server/include");
296  master()->add_program_path(server_path+"/server/");
297  master()->add_program_path(server_path+"/conf/");
298  master()->add_program_path(server_path+"/spm/");
299  master()->add_program_path(server_path+"/server/net/coal/");
300 
301  conn = ((program)"client_base.pike")();
302 
303  int start_time = time();
304 
305  werror("Connecting to sTeam server...\n");
306  while ( !conn->connect_server(options->host, options->port) )
307  {
308  if ( time() - start_time > 120 )
309  {
310  throw (({" Couldn't connect to server. Please check steam.log for details! \n", backtrace()}));
311  }
312  werror("Failed to connect... still trying ... (server running ?)\n");
313  sleep(10);
314  }
315 
316  ping();
317  if(lower_case(options->user) == "guest")
318  return options;
319 
320  mixed err;
321  int tries=3;
322  //readln->set_echo( 0 );
323  do
324  {
325  pw = Input.read_password( sprintf("Password for %s@%s", options->user,
326  options->host), "steam" );
327  //pw=readln->read(sprintf("passwd for %s@%s: ", options->user, options->host));
328  }
329  while((err = catch(conn->login(options->user, pw, 1))) && --tries);
330  //readln->set_echo( 1 );
331 
332  if ( err != 0 )
333  {
334  werror("Failed to log in!\nWrong Password!\n");
335  exit(1);
336  }
337  return options;
338 }
339 
340 mapping assign(object conn, object _Server, object users)
341 {
342  return ([
343  "_Server" : _Server,
344  "get_module" : _Server->get_module,
345  "get_factory" : _Server->get_factory,
346  "conn" : conn,
347  "find_object" : conn->find_object,
348  "users" : users,
349  "groups" : _Server->get_module("groups"),
350  "me" : users->lookup(options->user),
351  "edit" : applaunch,
352  "create" : create_object,
353  "list" : list,
354  "goto" : goto_room,
355  "title" : set_title,
356  "room" : desc_room,
357  "look" : look,
358  "take" : take,
359  "gothrough" : gothrough,
360 
361  // from database.h :
362  "_SECURITY" : _Server->get_module("security"),
363  "_FILEPATH" : _Server->get_module("filepath:tree"),
364  "_TYPES" : _Server->get_module("types"),
365  "_LOG" : _Server->get_module("log"),
366  "OBJ" : _Server->get_module("filepath:tree")->path_to_object,
367  "MODULE_USERS" : _Server->get_module("users"),
368  "MODULE_GROUPS" : _Server->get_module("groups"),
369  "MODULE_OBJECTS" : _Server->get_module("objects"),
370  "MODULE_SMTP" : _Server->get_module("smtp"),
371  "MODULE_URL" : _Server->get_module("url"),
372  "MODULE_ICONS" : _Server->get_module("icons"),
373  "SECURITY_CACHE" : _Server->get_module("Security:cache"),
374  "MODULE_SERVICE" : _Server->get_module("ServiceManager"),
375  "MOD" : _Server->get_module,
376  "USER" : _Server->get_module("users")->lookup,
377  "GROUP" : _Server->get_module("groups")->lookup,
378  "_ROOTROOM" : _Server->get_module("filepath:tree")->path_to_object("/"),
379  "_STEAMUSER" : _Server->get_module("users")->lookup("steam"),
380  "_ROOT" : _Server->get_module("users")->lookup("root"),
381  "_GUEST" : _Server->get_module("users")->lookup("guest"),
382  "_ADMIN" : _Server->get_module("users")->lookup("admin"),
383  "_WORLDUSER" : _Server->get_module("users")->lookup("everyone"),
384  "_AUTHORS" : _Server->get_module("users")->lookup("authors"),
385  "_REVIEWER" : _Server->get_module("users")->lookup("reviewer"),
386  "_BUILDER" : _Server->get_module("users")->lookup("builder"),
387  "_CODER" : _Server->get_module("users")->lookup("coder"),
388  ]);
389 }
390 
391 // create new sTeam objects
392 // with code taken from the web script create.pike
393 mixed create_object(string|void objectclass, string|void name, void|string desc, void|mapping data)
394 {
395  if(!objectclass && !name)
396  {
397  write("Usage: create(string objectclass, string name, void|string desc, void|mapping data\n");
398  return 0;
399  }
400  object _Server=conn->SteamObj(0);
401  object created;
402  object factory;
403 
404  if ( !stringp(objectclass))
405  return "No object type submitted";
406 
407  factory = _Server->get_factory(objectclass);
408 
409  switch(objectclass)
410  {
411  case "Exit":
412  if(!data->exit_from)
413  return "exit_from missing";
414  break;
415  case "Link":
416  if(!data->link_to)
417  return "link_to missing";
418  break;
419  }
420 
421  if(!data)
422  data=([]);
423  created = factory->execute(([ "name":name ])+ data );
424 
425  if(stringp(desc))
426  created->set_attribute("OBJ_DESC", desc);
427 
428 // if ( kind=="gallery" )
429 // {
430 // created->set_acquire_attribute("xsl:content", 0);
431 // created->set_attribute("xsl:content",
432 // ([ _STEAMUSER:_FILEPATH->path_to_object("/stylesheets/gallery.xsl") ])
433 // );
434 // }
435 
436 // created->move(this_user());
437 
438  return created;
439 }
440 
441 string getstring(int i)
442 {
443 // write("came in here\n");
444  string curpath = getpath();
445  if(i==1&&flag==1)
446  return curpath+"> ";
447  else if(i==1&&(flag==0))
448  return curpath+"~ ";
449  else if(i==2&&flag==1)
450  return curpath+">> ";
451  else if(i==2&&(flag==0))
452  return curpath+"~~ ";
453 }
454 
455 int list(string what)
456 {
457  if(what==""||what==0)
458  {
459  write("Wrong usage\n");
460  return 0;
461  }
462  string toappend="";
463  array display = get_list(what);
464  if(sizeof(display)==0)
465  toappend = "There are no "+what+" in this room.\n";
466  else if (display[0]=="Invalid command")
467  {
468  write(display+"\n");
469  }
470  else{
471  toappend = "Here is a list of all "+what+" in the current room\n";
472  write(toappend);
473 // The linux terminal has 80 characters. This character length is divided to 3 columns.
474 // The first two columns having char length of 27 and the last one having char length 26
475  int i = 0;
476  foreach(display,string str)
477  {
478  if(i==2)
479  {
480  write(sprintf("%-26s",str));
481  i=0;
482  }
483  else
484  {
485  write(sprintf("%-27s",str));
486  i++;
487  }
488  }
489  }
490  return 0;
491 }
492 
493 array get_list(string what,string|object|void lpath)
494 {
495 // string name;
496 // object to;
497  array gates=({}),containers=({}),documents=({}),rooms = ({}),rest=({});
498 // mapping s = ([ ]);
499  object pathobj;
500  if(!lpath)
501  pathobj = OBJ(getpath());
502  else if(stringp(lpath))
503  pathobj = OBJ(lpath);
504  else if(objectp(lpath))
505  pathobj = lpath;
506 // string pathfact = _Server->get_factory(pathobj)->query_attribute("OBJ_NAME");
507  mixed all = pathobj->get_inventory_by_class(0x3cffffff); //CLASS_ALL
508  foreach(all, object obj)
509  {
510  string fact_name = _Server->get_factory(obj)->query_attribute("OBJ_NAME");
511  string obj_name = obj->query_attribute("OBJ_NAME");
512 // write("normally : "+obj_name+"\n");
513  if(fact_name=="Document.factory")
514  documents = Array.push(documents,obj_name);
515 // write(obj_name+"\n");
516  else if(fact_name=="Exit.factory"){
517  string fullgate = obj_name+" : "+obj->get_exit()->query_attribute("OBJ_NAME");
518  gates = Array.push(gates,fullgate);
519 // write("in gates : "+fullgate+"\n");
520  }
521  else if(fact_name=="Container.factory")
522  containers = Array.push(containers,obj_name);
523 // write("in containers : "+obj_name+"\n");
524  else if(fact_name=="Room.factory")
525  rooms = Array.push(rooms,obj_name);
526  else
527  rest = Array.push(rest, obj_name);
528  }
529  if(what=="gates")
530  return gates;
531  else if(what=="rooms")
532  return rooms;
533  else if(what=="containers")
534  return containers;
535  else if(what=="files")
536  return documents;
537  else if(what=="others")
538  return rest;
539  else
540  return ({"Invalid command"});
541 }
542 
543 
544 int goto_room(string where)
545 {
546  string roomname="";
547  object pathobj;
548  //USER CANT GO TO A RUCKSACK. HE CAN JUST LOOK INSIDE RUCKSACK
549 /* if(where=="rucksack")
550  {
551  pathobj=users->lookup(options->user);
552  path="/home/~"+pathobj->query_attribute("OBJ_NAME");
553  roomname="Your rucksack";
554  }
555 */
556 // else
557 // {
558  pathobj = OBJ(where);
559  if(!pathobj) //Relative room checking
560  {
561  if(getpath()[-1]==47) //check last "/"
562  {
563  pathobj = OBJ(getpath()+where);
564  where=getpath()+where;
565  }
566  else
567  {
568  pathobj = OBJ(getpath()+"/"+where);
569  where=getpath()+"/"+where;
570  }
571  }
572  roomname = pathobj->query_attribute("OBJ_NAME");
573  string factory = _Server->get_factory(pathobj)->query_attribute("OBJ_NAME");
574  //DONT NEED THIS. NEED TO USE me->move() to these locations
575 // if(pathobj&&((factory=="Room.factory")||(factory=="User.factory")||(factory=="Container.factory")))
576 // path = where;
577  string oldpath = getpath();
578  mixed error = catch{
579  me->move(pathobj);
580  write("You are now inside "+roomname+"\n");
581  };
582 
583  if(error && pathobj)
584  {
585  write("Please specify path to room. Not a "+((factory/".")[0])+"\n");
586  me->move(OBJ(oldpath));
587  }
588  else if(error)
589  {
590  write("Please specify correct path to a room.\n");
591  }
592 // }
593 // roomname = pathobj->query_attribute("OBJ_NAME");
594 // write("You are now inside "+roomname+"\n");
595  return 0;
596 }
597 
598 int set_title(string desc)
599 {
600  if(users->lookup(options->user)->set_attribute("OBJ_DESC",desc))
601  write("You are now described as - "+desc+"\n");
602  else
603  write("Cannot set description.\n");
604  return 0;
605 }
606 
607 int desc_room()
608 {
609 // write("path : "+path+"\n");
610  object pathobj = OBJ(getpath());
611  string desc = pathobj->query_attribute("OBJ_DESC");
612 // write("desc : "+desc+"\n");
613  if((desc=="")||(Regexp.match("^ +$",desc)))
614  desc = "This room does not have a description yet.\n";
615  write("You are currently in "+pathobj->query_attribute("OBJ_NAME")+"\n"+desc+"\n");
616  return 0;
617 }
618 
619 int look(string|void str)
620 {
621  if(str)
622  {
623  write("Just type in 'look' to look around you\n");
624  return 0;
625  }
626  desc_room();
627  list("files");
628  write("---------------\n");
629  list("containers");
630  write("---------------\n");
631  list("gates");
632  write("---------------\n");
633  list("rooms");
634  write("---------------\n");
635  return 0;
636 }
637 
638 int take(string name)
639 {
640  string fullpath="";
641  if(getpath()[-1]==47) //check last "/"
642  fullpath = getpath()+name;
643  else
644  fullpath = getpath()+"/"+name;
645  object orig_file = OBJ(fullpath);
646  if(orig_file)
647  {
648  object dup_file = orig_file->duplicate();
649  dup_file->move(me);
650  write(name+" copied to your rucksack.\n");
651  }
652  else
653  write("Please mention a file in this room.");
654  return 0;
655 }
656 
657 int gothrough(string gatename)
658 {
659  string fullpath = "";
660  if(getpath()[-1]==47) //check last "/"
661  fullpath = getpath()+gatename;
662  else
663  fullpath = getpath()+"/"+gatename;
664  object gate = OBJ(fullpath);
665  if(gate)
666  {
667  object exit = gate->get_exit();
668  string exit_path1 = "",exit_path2 = "";
669 // exit_path1 = _Server->get_module("filepath:tree")->check_tilde(exit);
670 // exit_path2 = _Server->get_module("filepath:tree")->object_to_path(exit);
671 // if(exit_path1!="")
672 // goto_room(exit_path1);
673 // else if(exit_path2!="/void/"||exit_path2!="")
674 // goto_room(exit_path2);
675 // else
676 // write("Problem with object_to_path\n");
677  exit_path1 = exit->query_attribute("OBJ_PATH"); //change to object_to_path
678  if(exit_path1!="")
679  goto_room(exit_path1);
680  }
681  else
682  write(gatename+" is not reachable from current room\n");
683  return 0;
684 }
685 
686 int delete(string file_cont_name)
687 {
688  string fullpath="";
689  if(getpath()[-1]==47) //check last "/"
690  fullpath = getpath()+file_cont_name;
691  else
692  fullpath = getpath()+"/"+file_cont_name;
693  if(OBJ(fullpath))
694  return 0;
695  return 0;
696 }
697 
698 int create_ob(string type,string name)
699 {
700  string desc = readln->read("How would you describe it?\n");
701  mapping data = ([]);
702  type = String.capitalize(type);
703  if(type=="Exit")
704  {
705  object exit_to = OBJ(readln->read("Where do you want to exit to?(full path)\n"));
706  object exit_from = OBJ(getpath());
707  data = ([ "exit_from":exit_from, "exit_to":exit_to ]);
708  }
709  else if(type=="Link")
710  {
711  object link_to = OBJ(readln->read("Where does the link lead?\n"));
712  data = ([ "link_to":link_to ]);
713  }
714  object myobj = create_object(type,name,desc,data);
715  if(type=="Room")
716  myobj->move(OBJ(getpath()));
717 
718  return 0;
719 }
720 
721 int peek(string container)
722 {
723  string fullpath = "";
724  if(getpath()[-1]==47) //check last "/"
725  fullpath = getpath()+container;
726  else
727  fullpath = getpath()+"/"+container;
728  string pathfact = _Server->get_factory(OBJ(fullpath))->query_attribute("OBJ_NAME");
729  if(pathfact=="Room.factory")
730  {
731  write("Maybe you are looking for the command 'look'\n");
732  return 0;
733  }
734  if(pathfact!="Container.factory")
735  {
736  write("You can't peek into a "+pathfact[0..sizeof(pathfact)-8]+"\n");
737  return 0;
738  }
739  array conts = get_list("containers", fullpath);
740  array files = get_list("files", fullpath);
741  write("You peek into "+container+"\n\n");
742  display("containers", conts);
743  display("files", files);
744 }
745 
746 void display(string type, array strs)
747 {
748  if(sizeof(strs)==0)
749  write("There are no "+type+" here\n");
750  else if(sizeof(strs)==1)
751  write("There is 1 "+type[0..sizeof(type)-2]+" here\n");
752  else
753  write("There are "+sizeof(strs)+" "+type+" here\n");
754  foreach(strs, string str)
755  {
756  write(str+" ");
757  }
758  write("\n-----------------------\n");
759 }
760 
761 int inventory()
762 {
763  array conts = get_list("containers", me);
764  array files = get_list("files", me);
765  array others = get_list("others", me);
766  write("You check your inventory\n");
767  display("containers", conts);
768  display("files", files);
769  display("other files", others);
770 }
771 
772 int editfile(string filename)
773 {
774  string fullpath = "";
775  if(getpath()[-1]==47) //check last "/"
776  fullpath = getpath()+filename;
777  else
778  fullpath = getpath()+"/"+filename;
779  string pathfact = _Server->get_factory(OBJ(fullpath))->query_attribute("OBJ_NAME");
780  if(pathfact=="Document.factory")
781  applaunch(OBJ(fullpath),exitnow);
782  else
783  write("You can't edit a "+pathfact[0..sizeof(pathfact)-8]);
784  return 0;
785 }
786 
787 void exitnow()
788 {}
789 
790 string getpath()
791 {
792  return me->get_last_trail()->query_attribute("OBJ_PATH");
793 }
794 
795 constant stash_help_doc = #"This is a sTeam Advanced Shell. All the STASH commands work with normal pike commands. Tab completion is available for both STASH commands and pike commands.\n\n";
796 
797 
798 };