Utils/Console: Add short help to 'ls' output
[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
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::BuiltinPUSHD :
116             // The parser ensures, we have exactly one argument
117             if (skipping())
118                 pushd(command.commandPath());
119             else
120                 exec(output, command);
121             break;
122             
123         case ParseCommandInfo::BuiltinPOPD :
124             // The parser ensures, we have no arguments
125             popd();
126             break;
127             
128         case ParseCommandInfo::BuiltinEXIT :
129             if (skipping())
130                 break;
131             // The parser ensures, we have no arguments
132             exit();
133             break;
134
135         case ParseCommandInfo::BuiltinHELP :
136             if (skipping())
137                 break;
138             // The parser ensures, we have either one or no arguments
139             help( output, command.commandPath() );
140             break;
141
142         }
143     }
144     catch (InvalidPathException & ex) {
145         throw SyntaxErrorException("invalid path") << " '" << ex.path << "'";
146     }
147     catch (InvalidDirectoryException & ex) {
148         throw SyntaxErrorException("invalid directory") << " '" << ex.path << "'";
149     }
150     catch (InvalidCommandException &) {
151         throw SyntaxErrorException("invalid command");
152     }
153     catch (IgnoreCommandException &) {}
154 }
155
156 prefix_ void senf::console::Executor::exec(std::ostream & output,
157                                            ParseCommandInfo const & command)
158 {
159     try {
160         GenericNode & node ( traverseNode(command.commandPath()) );
161         DirectoryNode * dir ( dynamic_cast<DirectoryNode*>(&node) );
162         if ( dir ) {
163             if (! command.tokens().empty())
164                 throw InvalidCommandException();
165             if (command.builtin() == ParseCommandInfo::BuiltinPUSHD)
166                 pushd( command.commandPath() );
167             else if (autocd_) {
168                 cd(command.commandPath());
169             }
170             else
171                 throw InvalidCommandException();
172         } else {
173             boost::any rv;
174             dynamic_cast<CommandNode &>(node)(rv, output, command);
175             if (command.builtin() == ParseCommandInfo::BuiltinPUSHD) {
176                 DirectoryNode::ptr rvdir;
177                 try {
178                     rvdir = boost::any_cast<DirectoryNode::ptr>(rv);
179                 }
180                 catch (boost::bad_any_cast &) {
181                     throw InvalidCommandException();
182                 }
183                 Path newDir (cwd_);
184                 newDir.push_back(rvdir);
185                 dirstack_.push_back(Path());
186                 dirstack_.back().swap(cwd_);
187                 cwd_.swap(newDir);
188             }
189         }
190     }
191     catch (IgnoreCommandException &) {
192         if (command.builtin() == ParseCommandInfo::BuiltinPUSHD) {
193             dirstack_.push_back(Path());
194             dirstack_.back().swap(cwd_);
195         }
196         else
197             throw;
198     }
199 }
200
201
202 prefix_ void senf::console::Executor::cd(ParseCommandInfo::TokensRange dir)
203 {
204     if (dir.size() == 1 && *dir.begin() == WordToken("-")) {
205         cwd_.swap(oldCwd_);
206         (void) cwd(); // Prune any expired items
207     }
208     else {
209         // We need to use a temporary so an error somewhere while traversing the dir does not cause
210         // the current directory to change.
211         Path newDir (cwd_);
212         traverseDirectory(dir, newDir);
213         oldCwd_.swap(cwd_);
214         cwd_.swap(newDir);
215     }
216 }
217
218 prefix_ void senf::console::Executor::ls(std::ostream & output,
219                                          ParseCommandInfo::TokensRange path)
220 {
221     Path dir (cwd_);
222     traverseDirectory(path, dir);
223     DirectoryNode & node (*dir.back().lock());
224     DirectoryNode::child_iterator i (node.children().begin());
225     DirectoryNode::child_iterator const i_end (node.children().end());
226     boost::format fmt ("%s%s  %|20t|%.59s\n");
227     for (; i != i_end; ++i)
228         output << fmt
229             % i->first
230             % ( i->second->isDirectory()
231                 ? "/"
232                 : i->second->isLink()
233                 ? "@"
234                 : "" )
235             % i->second->shorthelp();
236 }
237
238 prefix_ void senf::console::Executor::pushd(ParseCommandInfo::TokensRange dir)
239 {
240     Path newDir (cwd_);
241     if (! skipping()) {
242         try {
243             traverseDirectory(dir, newDir);
244         }
245         catch (IgnoreCommandException &) {
246             newDir.clear();
247         }
248     }
249     dirstack_.push_back(Path());
250     dirstack_.back().swap(cwd_);
251     cwd_.swap(newDir);
252 }
253
254 prefix_ void senf::console::Executor::popd()
255 {
256     if (! dirstack_.empty()) {
257         cwd_.swap(dirstack_.back());
258         dirstack_.pop_back();
259     }
260 }
261
262 prefix_ void senf::console::Executor::exit()
263 {
264     throw ExitException();
265 }
266
267 prefix_ void senf::console::Executor::help(std::ostream & output,
268                                            ParseCommandInfo::TokensRange path)
269 {
270     GenericNode const & node (traverseNode(path));
271     // output << prettyName(typeid(node)) << " at " << node.path() << "\n\n";
272     node.help(output);
273     output << std::flush;
274 }
275
276 prefix_ senf::console::GenericNode &
277 senf::console::Executor::traverseNode(ParseCommandInfo::TokensRange const & path)
278 {
279     if (path.empty())
280         return *cwd_.back().lock();
281     try {
282         Path dir (cwd_);
283         traverseDirectory(boost::make_iterator_range(
284                               path.begin(),
285                               boost::prior(path.end())),
286                           dir);
287         // For auto-cd support we need to check against '.' and '..' here too
288         Token const & tok (*boost::prior(path.end()));
289         if (tok == WordToken("..")) {
290             if (dir.size() > 1)
291                 dir.pop_back();
292             return *dir.back().lock();
293         }
294         DirectoryNode & base (*dir.back().lock());
295         if (tok == WordToken(".") || tok == NoneToken())
296             return base;
297         std::string const & name (complete(base, tok.value()));
298         if (policy_)
299             policy_( base, name );
300         return dir.back().lock()->get(name);
301     }
302     catch (UnknownNodeNameException &) {
303         throw InvalidPathException(
304             senf::stringJoin(
305                 senf::make_transform_range(path, boost::bind(&Token::value, _1)),
306                 "/"));
307     }
308 }
309
310 prefix_ void
311 senf::console::Executor::traverseDirectory(ParseCommandInfo::TokensRange const & path,
312                                            Path & dir)
313 {
314     std::string errorPath;
315     try {
316         ParseCommandInfo::TokensRange::const_iterator i (path.begin());
317         ParseCommandInfo::TokensRange::const_iterator const i_end (path.end());
318         for (; i != i_end; ++i) {
319             if (i != path.begin())
320                 errorPath += "/";
321             errorPath += i->value();
322             if (*i == NoneToken()) {
323                 if (i == path.begin()) {
324                     dir.clear();
325                     dir.push_back(root_);
326                 }
327             }
328             else if (*i == WordToken("..")) {
329                 if (dir.size() > 1)
330                     dir.pop_back();
331             }
332             else if (*i ==  WordToken("."))
333                 ;
334             else {
335                 DirectoryNode & base (*dir.back().lock());
336                 std::string name (complete(base, i->value()));
337                 if (policy_)
338                     policy_( base, name );
339                 dir.push_back(base[name].thisptr());
340             }
341         }
342     }
343     catch (std::bad_cast &) {
344         throw InvalidDirectoryException(errorPath);
345     }
346     catch (UnknownNodeNameException &) {
347         throw InvalidDirectoryException(errorPath);
348     }
349 }
350
351 prefix_ std::string senf::console::Executor::complete(DirectoryNode & dir,
352                                                       std::string const & name)
353 {
354     if (! dir.hasChild(name)) {
355         DirectoryNode::ChildrenRange completions (dir.completions(name));
356         if (has_one_elt(completions))
357             return completions.begin()->first;
358     }
359     return name;
360 }
361
362 prefix_ void senf::console::senf_console_format_value(DirectoryNode::ptr value,
363                                                       std::ostream & os)
364 {
365     if (value)
366         os << "<Directory at '" << value->path() << "'>";
367     else
368         os << "<Null Directory>";
369 }
370
371 ///////////////////////////////cc.e////////////////////////////////////////
372 #undef prefix_
373 //#include "Executor.mpp"
374
375 \f
376 // Local Variables:
377 // mode: c++
378 // fill-column: 100
379 // comment-column: 40
380 // c-file-style: "senf"
381 // indent-tabs-mode: nil
382 // ispell-local-dictionary: "american"
383 // compile-command: "scons -u test"
384 // End: