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