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