Utils/Termlib: Add width() member to AbstractTerminal
[senf.git] / Utils / Console / Executor.cc
1 // $Id$
2 //
3 // Copyright (C) 2008 
4 // Fraunhofer Institute for Open Communication Systems (FOKUS)
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY
6 //     Stefan Bund <g0dil@berlios.de>
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the
20 // Free Software Foundation, Inc.,
21 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23 /** \file
24     \brief Executor non-inline non-template implementation */
25
26 #include "Executor.hh"
27 //#include "Executor.ih"
28
29 // Custom includes
30 #include <boost/utility.hpp>
31 #include <boost/range/iterator_range.hpp>
32 #include <boost/bind.hpp>
33 #include <boost/format.hpp>
34 #include "../../Utils/senfassert.hh"
35 #include "../../Utils/Range.hh"
36 #include "../../Utils/String.hh"
37 #include "../../Utils/range.hh"
38 #include "Server.hh"
39
40 //#include "Executor.mpp"
41 #define prefix_
42 ///////////////////////////////cc.p////////////////////////////////////////
43
44 namespace {
45
46     struct TraversTokens {
47         typedef std::string const & result_type;
48         result_type operator()(senf::console::Token const & token) const {
49             return token.value();
50         }
51     };
52     
53 }
54
55 ///////////////////////////////////////////////////////////////////////////
56 // senf::console::Executor
57
58 prefix_ senf::console::DirectoryNode & senf::console::Executor::cwd()
59     const
60 {
61     SENF_ASSERT( ! cwd_.empty() );
62     while (cwd_.size()>1 && (cwd_.back().expired() || ! cwd_.back().lock()->active()))
63         cwd_.pop_back();
64     return * cwd_.back().lock();
65 }
66
67 prefix_ std::string senf::console::Executor::cwdPath()
68     const
69 {
70     if (skipping())
71         return "";
72     (void) cwd(); // ensure, cwd is live.
73     return "/" + senf::stringJoin(
74         senf::make_transform_range(
75             boost::make_iterator_range(boost::next(cwd_.begin()), cwd_.end()),
76             boost::bind(&DirectoryNode::name, boost::bind(&DirectoryNode::weak_ptr::lock, _1))),
77         "/" );
78 }
79
80 prefix_ void senf::console::Executor::execute(std::ostream & output,
81                                               ParseCommandInfo const & command)
82 {
83     SENF_LOG(( "Executing: " << command ));
84
85     if (! skipping())
86         (void) cwd(); // Prune the cwd path of expired entries
87
88     try {
89         switch(command.builtin()) {
90         case ParseCommandInfo::NoBuiltin : 
91             if (skipping())
92                 return;
93             exec(output, command);
94             break;
95
96         case ParseCommandInfo::BuiltinCD :
97             if (skipping())
98                 break;
99             try {
100                 // The parser ensures, we have exactly one argument
101                 cd(command.commandPath());
102             }
103             catch (IgnoreCommandException &) {
104                 throw SyntaxErrorException(
105                     "'cd' cannot be skipped (don't use 'cd' in conf-files)");
106             }
107             break;
108             
109         case ParseCommandInfo::BuiltinLS :
110             if (skipping())
111                 break;
112             // The parser ensures, we have either one or no argument
113             ls( output, command.commandPath() );
114             break;
115
116         case ParseCommandInfo::BuiltinLR :
117             if (skipping())
118                 break;
119             // The parser ensures, we have either one or no argument
120             lr( output, command.commandPath() );
121             break;
122
123         case ParseCommandInfo::BuiltinPUSHD :
124             // The parser ensures, we have exactly one argument
125             if (skipping())
126                 pushd(command.commandPath());
127             else
128                 exec(output, command);
129             break;
130             
131         case ParseCommandInfo::BuiltinPOPD :
132             // The parser ensures, we have no arguments
133             popd();
134             break;
135             
136         case ParseCommandInfo::BuiltinEXIT :
137             if (skipping())
138                 break;
139             // The parser ensures, we have no arguments
140             exit();
141             break;
142
143         case ParseCommandInfo::BuiltinHELP :
144             if (skipping())
145                 break;
146             // The parser ensures, we have either one or no arguments
147             help( output, command.commandPath() );
148             break;
149
150         }
151     }
152     catch (InvalidPathException & ex) {
153         throw SyntaxErrorException("invalid path") << " '" << ex.path << "'";
154     }
155     catch (InvalidDirectoryException & ex) {
156         throw SyntaxErrorException("invalid directory") << " '" << ex.path << "'";
157     }
158     catch (InvalidCommandException &) {
159         throw SyntaxErrorException("invalid command");
160     }
161     catch (IgnoreCommandException &) {}
162 }
163
164 prefix_ void senf::console::Executor::exec(std::ostream & output,
165                                            ParseCommandInfo const & command)
166 {
167     try {
168         GenericNode & node ( traverseNode(command.commandPath()) );
169         DirectoryNode * dir ( dynamic_cast<DirectoryNode*>(&node) );
170         if ( dir ) {
171             if (! command.tokens().empty())
172                 throw InvalidCommandException();
173             if (command.builtin() == ParseCommandInfo::BuiltinPUSHD)
174                 pushd( command.commandPath() );
175             else if (autocd_) {
176                 cd(command.commandPath());
177             }
178             else
179                 throw InvalidCommandException();
180         } else {
181             boost::any rv;
182             dynamic_cast<CommandNode &>(node)(rv, output, command);
183             if (command.builtin() == ParseCommandInfo::BuiltinPUSHD) {
184                 DirectoryNode::ptr rvdir;
185                 try {
186                     rvdir = boost::any_cast<DirectoryNode::ptr>(rv);
187                 }
188                 catch (boost::bad_any_cast &) {
189                     throw InvalidCommandException();
190                 }
191                 Path newDir (cwd_);
192                 newDir.push_back(rvdir);
193                 dirstack_.push_back(Path());
194                 dirstack_.back().swap(cwd_);
195                 cwd_.swap(newDir);
196             }
197         }
198     }
199     catch (IgnoreCommandException &) {
200         if (command.builtin() == ParseCommandInfo::BuiltinPUSHD) {
201             dirstack_.push_back(Path());
202             dirstack_.back().swap(cwd_);
203         }
204         else
205             throw;
206     }
207 }
208
209
210 prefix_ void senf::console::Executor::cd(ParseCommandInfo::TokensRange dir)
211 {
212     if (dir.size() == 1 && *dir.begin() == WordToken("-")) {
213         cwd_.swap(oldCwd_);
214         (void) cwd(); // Prune any expired items
215     }
216     else {
217         // We need to use a temporary so an error somewhere while traversing the dir does not cause
218         // the current directory to change.
219         Path newDir (cwd_);
220         traverseDirectory(dir, newDir);
221         oldCwd_.swap(cwd_);
222         cwd_.swap(newDir);
223     }
224 }
225
226 prefix_ void senf::console::Executor::ls(std::ostream & output,
227                                          ParseCommandInfo::TokensRange path)
228 {
229     unsigned width (80);
230     try {
231         width = senf::console::Client::get(output).width();
232     }
233     catch (std::bad_cast &)
234     {}
235     if (width<60)
236         width = 80;
237     width -= 28+1;
238     Path dir (cwd_);
239     traverseDirectory(path, dir);
240     DirectoryNode & node (*dir.back().lock());
241     DirectoryNode::child_iterator i (node.children().begin());
242     DirectoryNode::child_iterator const i_end (node.children().end());
243     boost::format fmt ("%s%s  %|28t|%s\n");
244     for (; i != i_end; ++i)
245         output << fmt
246             % i->first
247             % ( i->second->isDirectory()
248                 ? "/"
249                 : i->second->isLink()
250                 ? "@"
251                 : "" )
252             % i->second->shorthelp().substr(0,width);
253 }
254
255 namespace {
256
257     typedef std::map<senf::console::DirectoryNode*,std::string> NodesMap;
258
259     void dolr(std::ostream & output, unsigned width, NodesMap & nodes, std::string const & base, 
260               unsigned level, senf::console::DirectoryNode & node)
261     {
262         boost::format fmt ("%s%s%s  %|40t|%s\n");
263         std::string pad (2*level, ' ');
264         senf::console::DirectoryNode::child_iterator i (node.children().begin());
265         senf::console::DirectoryNode::child_iterator const i_end (node.children().end());
266         for (; i != i_end; ++i) {
267             output << fmt
268                 % pad
269                 % i->first
270                 % ( i->second->isDirectory()
271                     ? "/"
272                     : i->second->isLink()
273                     ? "@"
274                     : "" )
275                 % i->second->shorthelp().substr(0,width);
276             if (i->second->followLink().isDirectory()) {
277                 senf::console::DirectoryNode & subnode (
278                     static_cast<senf::console::DirectoryNode&>(i->second->followLink()));
279                 NodesMap::iterator j (nodes.find(&subnode));
280                 if (j == nodes.end()) {
281                     std::string subbase (base);
282                     if (! subbase.empty())
283                         subbase += "/";
284                     subbase += i->first;
285                     nodes.insert(std::make_pair(&subnode, subbase));
286                     dolr(output, width, nodes, subbase, level+1, subnode);
287                 } else
288                     output << pad << "  -> " << j->second << "\n";
289             }
290         }
291     }
292
293 }
294
295 prefix_ void senf::console::Executor::lr(std::ostream & output,
296                                          ParseCommandInfo::TokensRange path)
297 {
298     unsigned width (80);
299     try {
300         width = senf::console::Client::get(output).width();
301     }
302     catch (std::bad_cast &)
303     {}
304     if (width<60)
305         width = 80;
306     width -= 40+1;
307     Path dir (cwd_);
308     traverseDirectory(path, dir);
309     DirectoryNode & node (*dir.back().lock());
310     NodesMap nodes;
311     dolr(output, width, nodes, "", 0, node);
312 }
313
314 prefix_ void senf::console::Executor::pushd(ParseCommandInfo::TokensRange dir)
315 {
316     Path newDir (cwd_);
317     if (! skipping()) {
318         try {
319             traverseDirectory(dir, newDir);
320         }
321         catch (IgnoreCommandException &) {
322             newDir.clear();
323         }
324     }
325     dirstack_.push_back(Path());
326     dirstack_.back().swap(cwd_);
327     cwd_.swap(newDir);
328 }
329
330 prefix_ void senf::console::Executor::popd()
331 {
332     if (! dirstack_.empty()) {
333         cwd_.swap(dirstack_.back());
334         dirstack_.pop_back();
335     }
336 }
337
338 prefix_ void senf::console::Executor::exit()
339 {
340     throw ExitException();
341 }
342
343 prefix_ void senf::console::Executor::help(std::ostream & output,
344                                            ParseCommandInfo::TokensRange path)
345 {
346     GenericNode const & node (traverseNode(path));
347     // output << prettyName(typeid(node)) << " at " << node.path() << "\n\n";
348     node.help(output);
349     output << std::flush;
350 }
351
352 prefix_ senf::console::GenericNode &
353 senf::console::Executor::traverseNode(ParseCommandInfo::TokensRange const & path)
354 {
355     if (path.empty())
356         return *cwd_.back().lock();
357     try {
358         Path dir (cwd_);
359         traverseDirectory(boost::make_iterator_range(
360                               path.begin(),
361                               boost::prior(path.end())),
362                           dir);
363         // For auto-cd support we need to check against '.' and '..' here too
364         Token const & tok (*boost::prior(path.end()));
365         if (tok == WordToken("..")) {
366             if (dir.size() > 1)
367                 dir.pop_back();
368             return *dir.back().lock();
369         }
370         DirectoryNode & base (*dir.back().lock());
371         if (tok == WordToken(".") || tok == NoneToken())
372             return base;
373         std::string const & name (complete(base, tok.value()));
374         if (policy_)
375             policy_( base, name );
376         return dir.back().lock()->get(name);
377     }
378     catch (UnknownNodeNameException &) {
379         throw InvalidPathException(
380             senf::stringJoin(
381                 senf::make_transform_range(path, boost::bind(&Token::value, _1)),
382                 "/"));
383     }
384 }
385
386 prefix_ void
387 senf::console::Executor::traverseDirectory(ParseCommandInfo::TokensRange const & path,
388                                            Path & dir)
389 {
390     std::string errorPath;
391     try {
392         ParseCommandInfo::TokensRange::const_iterator i (path.begin());
393         ParseCommandInfo::TokensRange::const_iterator const i_end (path.end());
394         for (; i != i_end; ++i) {
395             if (i != path.begin())
396                 errorPath += "/";
397             errorPath += i->value();
398             if (*i == NoneToken()) {
399                 if (i == path.begin()) {
400                     dir.clear();
401                     dir.push_back(root_);
402                 }
403             }
404             else if (*i == WordToken("..")) {
405                 if (dir.size() > 1)
406                     dir.pop_back();
407             }
408             else if (*i ==  WordToken("."))
409                 ;
410             else {
411                 DirectoryNode & base (*dir.back().lock());
412                 std::string name (complete(base, i->value()));
413                 if (policy_)
414                     policy_( base, name );
415                 dir.push_back(base[name].thisptr());
416             }
417         }
418     }
419     catch (std::bad_cast &) {
420         throw InvalidDirectoryException(errorPath);
421     }
422     catch (UnknownNodeNameException &) {
423         throw InvalidDirectoryException(errorPath);
424     }
425 }
426
427 prefix_ std::string senf::console::Executor::complete(DirectoryNode & dir,
428                                                       std::string const & name)
429 {
430     if (! dir.hasChild(name)) {
431         DirectoryNode::ChildrenRange completions (dir.completions(name));
432         if (has_one_elt(completions))
433             return completions.begin()->first;
434     }
435     return name;
436 }
437
438 prefix_ void senf::console::senf_console_format_value(DirectoryNode::ptr value,
439                                                       std::ostream & os)
440 {
441     if (value)
442         os << "<Directory at '" << value->path() << "'>";
443     else
444         os << "<Null Directory>";
445 }
446
447 ///////////////////////////////cc.e////////////////////////////////////////
448 #undef prefix_
449 //#include "Executor.mpp"
450
451 \f
452 // Local Variables:
453 // mode: c++
454 // fill-column: 100
455 // comment-column: 40
456 // c-file-style: "senf"
457 // indent-tabs-mode: nil
458 // ispell-local-dictionary: "american"
459 // compile-command: "scons -u test"
460 // End: