939113b4210fd00d72bbaaeab957335723648660
[senf.git] / Console / Node.hh
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 Node public header */
25
26 /** \defgroup node_tree The node tree
27     
28     The console/config node tree is the central data-structure of the library. Into this tree, all
29     commands and parameters are entered. The tree is then exposed using a file-system like
30     interface.
31     
32     \autotoc
33
34     \section console_tree The tree
35
36     We will start by giving a more complete example. This example contains most of the stuff needed
37     for using the console/config library.
38
39     \code
40     // Define callback function.
41     void mycommand(std::ostream & os, senf::console::ParseCommandInfo const & command)
42     {
43         // ...
44         os << "!! Important message ...\n";
45     }
46
47     class SomeClass
48     {
49     public:
50         // Declare a directory node (proxy) for use by this class. This must be public so we can add
51         // it to the node tree later.
52         senf::console::ScopedDirectory<SomeClass> dir;
53
54         SomeClass() : dir(this) 
55         {
56             // You may document the directory here or later when adding it to the tree
57             dir.doc("Manager for something");
58
59             // Add a member function (the pointer-to-member is automatically bound to this instance)
60             dir.add("member", &SomeClass::member)
61                 .doc("Do the member operation");
62         }
63
64         void member(std::ostream & os, senf::console::ParseCommandInfo const & command)
65         {
66             // ...
67         }
68     };
69
70     int main(int, char**)
71     {
72         // Provide global documentation
73         senf::console::root()
74             .doc("This is someServer server");
75
76         // Add a new directory to the root and document it. All the mutators return the node object
77         // itself so operations can be chained.
78         senf::console::DirectoryNode & mydir (
79                 .mkdir("myserver")
80                 .doc("My server specific directory"));
81
82         // Add a command to that directory
83         mydir.add("mycommand", &mycommand)
84             .doc("mycommand <foo> [<bar>]\n\n"
85                  "If <bar> is given, flurgle the <foo>, otherwise burgle it");
86
87         // Create a SomeClass instance and add it's directory.
88         SomeClass someClass;
89         mydir.add("someClass", someClass.dir);
90
91         // Start the interactive console server
92         senf::console::Server::start(senf::INet4SocketAddress(senf::INet4Address::None, 23232u))
93             .name("someServer");
94     }
95     \endcode
96
97     \subsection console_nodes Node types
98
99     The console/config library tree consists of two basic node types:
100     
101     \li senf::console::DirectoryNode provides internal nodes with an arbitrary number of children
102     \li senf::console::CommandNode describes a command entry in the tree
103
104     senf::console::CommandNode is the base-class of all command nodes of which there are several,
105     depending on the type of command.
106
107     There is a single root node, the senf::console::DirectoryNode called senf::console::root(). From
108     this node, the tree is traversed.
109
110     All nodes are allocated on the heap and are managed using a smart pointer.
111     
112     \subsection console_manipulate Manipulating the node tree
113
114     There are several ways to add nodes to the tree:
115
116     \li A senf::console::DirectoryNode can be added using senf::console::DirectoryNode::mkdir().
117     \li An arbitrary node can be created and then (possibly later) added to the tree using the
118         corresponding senf::console::DirectoryNode::add() overload.
119     \li A senf::console::CommandNode is normally added to the tree by directly adding a callback
120         using one of the overloaded senf::console::DirectoryNode::add() members. See \ref
121         console_commands.
122
123     When directly adding a node callback, the type of node added depends on the type of
124     callback. The callback types which can be added are listed at \ref console_callbacks.
125     
126     \code
127     void callback(std::ostream & os, senf::console::ParseCommandInfo const & command) { ... }
128     // ...
129     myDirectory.add("foo",&callback);
130     \endcode
131
132     Every node is identified among it's siblings by it's name. The name of the node is set when
133     adding the node to the tree. If the name is empty or non-unique, a unique name will be
134     automatically provided.
135
136     To remove a node from the tree, just use the nodes senf::console::GenericNode::unlink() or the
137     parents senf::console::DirectoryNode::remove() member. This call removes the node from it's
138     parent and returns a (smart) node pointer.
139
140     \li If you ignore the return value, the node (and it's children) will be deleted.
141     \li Alternatively, you may store away the node and re-attach it later.
142     \li An node (or subtree) can be moved to a different place by unlinking the node at it's old
143         place and re-adding it at it's new location.
144     \li To rename a node, unlink and re-add it with a different name.
145
146     \code
147     myDirectory.add("bar", myDirectory.remove("foo"));
148     \endcode
149
150     \subsection console_node_param Assigning additional node parameters
151
152     Depending on the node type added, additional node parameters may be set. For example, every node
153     has a documentation parameter which is used by the online-help system. To assign these
154     parameters, the node exposes corresponding member functions. Since
155     senf::console::DirectoryNode::add() returns the newly added node by reference, additional
156     parameters may just be added to the end of the add command:
157     \code
158     myDirectory.add("foo",&fooCallback).doc("The foo method");
159     \endcode
160     Since the parameter setters all return the node reference, additional parameters may just be
161     added to the end of the command.
162     
163     \subsection console_tree_traverse Traversing the tree
164
165     The simplest way to access tree elements is to save the return value of the
166     senf::console::DirectoryNode::add() members. However, saving the reference will not ensure, that
167     the node is not removed. If the node might be removed from the tree, you should use a smart
168     pointer (either <tt>ptr</tt> or <tt>weak_ptr</tt>) to hold the node.
169
170     Another possibility is to traverse the tree explicitly. For this purpose, the operators '[]' and
171     '()' have been overloaded in senf::console::DirectoryNode.
172     \code
173     senf::console::root().getDirectory("myDirectory").getCommand("foo")
174     \\ or more concise but otherwise completely identical
175     senf::console::root()["myDirectory"]("foo")
176     \endcode
177
178     getDirectory and the '[]' operator will return a senf::console::DirectoryNode whereas getCommand
179     and the '()' operator will return a senf::console::CommandNode. If the node is not found or is
180     not of the correct type, an exception will be raised.
181
182     \section console_object_dir Assigning a directory to an object instance
183
184     Most objects will register several commands. So it makes sense for these objects to manage their
185     own directory. Since directories are however allocated on the heap, they cannot be directly
186     added to a class. To facilitate this usage, the senf::console::ScopedDirectory is used. This
187     class provides a senf::console::DirectoryNode facade. Internally, it automatically creates a
188     senf::console::DirectoryNode to which all calls are forwarded.
189
190     The senf::console::ScopedDirectory member should be declared public. This allows the user of the
191     class to add the node to the tree.
192  */
193
194 #ifndef HH_Node_
195 #define HH_Node_ 1
196
197 // Custom includes
198 #include <map>
199 #include <boost/shared_ptr.hpp>
200 #include <boost/weak_ptr.hpp>
201 #include <boost/enable_shared_from_this.hpp>
202 #include <boost/utility.hpp>
203 #include <boost/range/iterator_range.hpp>
204 #include <boost/typeof/typeof.hpp>
205 #include <boost/type_traits/remove_reference.hpp>
206 #include "../Utils/Exception.hh"
207 #include "../Utils/mpl.hh"
208 #include "../Utils/Logger/SenfLog.hh"
209 #include "../Utils/type_traits.hh"
210 #include "Parse.hh"
211
212 //#include "Node.mpp"
213 ///////////////////////////////hh.p////////////////////////////////////////
214
215 namespace senf {
216 namespace console {
217
218     class LinkNode;
219     class DirectoryNode;
220     class CommandNode;
221
222     DirectoryNode & root();
223
224     /** \brief Config/console node tree base-class
225
226         GenericNode is the base class of all node objects. There are two basic node types derived
227         from GenericNode:  DirectoryNode and CommandNode.
228
229         All nodes are dynamically allocated and automatically managed using reference counting.
230
231         All nodes are normally linked into a single tree which root node is
232         senf::console::root(). Nodes may however be orphaned (not linked to the tree) either
233         directly (the node has no parent) or indirectly (the node has a parent but is part of an
234         orphaned subtree which is not linked to the root node).
235
236         Every active (non-orphaned) node (except the root() node) has a non-empty node name. This
237         name is assigned to the node when adding the node to the tree.
238
239         \ingroup node_tree
240       */
241     class GenericNode 
242         : public boost::enable_shared_from_this<GenericNode>
243     {
244         SENF_LOG_CLASS_AREA();
245     public:
246         ///////////////////////////////////////////////////////////////////////////
247         // Types
248
249         typedef boost::shared_ptr<GenericNode> ptr;
250         typedef boost::shared_ptr<GenericNode const> cptr;
251         typedef boost::weak_ptr<GenericNode> weak_ptr;
252
253         ///////////////////////////////////////////////////////////////////////////
254
255         virtual ~GenericNode();
256
257         std::string const & name() const; ///< Node name
258         boost::shared_ptr<DirectoryNode> parent() const; ///< Parent node
259                                         /**< May be null, if the node is the root node or if it is
260                                              not linked to the tree */
261
262         std::string path() const;       ///< Node path
263                                         /**< The node path is built by joining the names of all
264                                              parent nodes with '/' chars. */
265         std::string path(DirectoryNode const & root) const;       
266                                         ///< Node path up to \a root
267                                         /**< The node path is built by joining the names of all
268                                              parent nodes up to \a root with '/' chars. */
269
270         ptr unlink();                   ///< Remove node from it's parent directory
271                                         /**< You may either discard the return value and thereby
272                                              dispose the node or may re-attach the node at some
273                                              other place using DirectoryNode::add(). */
274
275         bool active() const;            ///< \c true, if node is attached to the root() node
276
277         void help(std::ostream & output) const; /// Write help info to \a output
278
279         ptr thisptr();                  ///< Get smart pointer to node
280         cptr thisptr() const;           ///< Get smart pointer to node (const)
281
282         bool isChildOf(DirectoryNode & parent) const;
283                                         ///< \c true, if node is a child of \a parent
284                                         /**< Will also return \c true, if \a parent is the current
285                                              node. */
286
287         bool operator== (GenericNode & other) const;
288                                         /// \c true, if this and \a other are the same node
289         bool operator!= (GenericNode & other) const;
290                                         /// \c true, if this and \a other are different nodes
291
292         bool isDirectory() const;       ///< \c true, if this is a drectory node
293         bool isLink() const;            ///< \c true, if this is a link node
294         bool isCommand() const;         ///< \c true, if this is a command node
295
296     protected:
297         GenericNode();
298
299         void name(std::string const & name);
300
301 #ifndef DOXYGEN
302     private:
303 #else
304     public:
305 #endif
306         virtual void v_help(std::ostream & output) const = 0;
307                                         ///< Provide help information
308                                         /**< This member must be implemented in derived classes
309                                              to provide node specific help information. */
310
311     private:
312         std::string name_;
313         DirectoryNode * parent_;
314
315         friend class DirectoryNode;
316     };
317
318     /** \brief Config/console tree link node
319
320         A LinkNode references another node and provides an additional alias name for that node. A
321         LinkNode works like a mixture of UNIX symlinks and hardlinks: It is an explicit link like a
322         UNIX symlink but references another node directly (not via it's path) like a UNIX
323         hardlink. Therefore, a LinkNode works across chroot().
324       */
325     class LinkNode
326         : public GenericNode
327     {
328     public:
329         ///////////////////////////////////////////////////////////////////////////
330         // Types
331
332         typedef boost::shared_ptr<LinkNode> ptr;
333         typedef boost::shared_ptr<LinkNode const> cptr;
334         typedef boost::weak_ptr<LinkNode> weak_ptr;
335
336         ///////////////////////////////////////////////////////////////////////////
337         ///\name Structors and default members
338         ///@{
339         
340         static ptr create(GenericNode & node); ///< Create new link node.
341                                         /**< You should normally use DirectoryNode::link() to
342                                              create a link node. */
343         
344         ///@}
345         ///////////////////////////////////////////////////////////////////////////
346
347         GenericNode & follow() const;   ///< Access the referenced node
348
349     protected:
350
351     private:
352         explicit LinkNode(GenericNode & node);
353
354         virtual void v_help(std::ostream &) const;
355
356         GenericNode::ptr node_;
357     };
358
359     class SimpleCommandNode;
360
361     /** \brief Internal: Node creation helper traits
362         
363         This class is used internally to find out the type of node to create for a specific argument
364         type. 
365      */
366     template <class Object>
367     struct NodeCreateTraits
368     {
369         typedef BOOST_TYPEOF_TPL( senf_console_add_node( 
370                                       * static_cast<DirectoryNode *>(0),
371                                       * static_cast<std::string const *>(0),
372                                       * static_cast<Object *>(0),
373                                       0) ) base_type;
374         typedef typename senf::remove_cvref<base_type>::type value_type;
375
376         typedef typename value_type::node_type NodeType;
377         typedef typename value_type::return_type result_type;
378
379         /// Internal
380         struct Creator {
381             static result_type create(DirectoryNode & node, std::string const & name, 
382                                       Object & ob);
383         };
384     };
385
386     /** \brief Config/console tree directory node
387
388         This node type provides the internal and root nodes of the tree. It allows to add arbitrary
389         children and supports directory traversal.
390         
391         Nodes are normally not instantiated manually but are created by the DirectoryNode via
392         mkdir() or add(). Special add() members however allow externally allocated node objects.
393
394         Nodes may be added to the tree only once, otherwise chaos will ensue. Since nodes are always
395         managed dynamically, there is a special ScopedDirectory proxy template which provides a
396         DirectoryNode facade. ScopedDirectory is used if a class wants to manage it's own directory
397         as a data member.
398
399         Every node is assigned a (new) name when it is added to a directory. If the directory
400         already has an entry of that name, the name is made unique by appending a suffix of the form
401         '-n' where n is a number starting at 1. If the name is empty, int is set to 'unnamed' and
402         then uniquified as above. Automatically providing unique names simplifies adding
403         configuration/console support to generic components.
404
405         \ingroup node_tree
406       */
407     class DirectoryNode : public GenericNode
408     {
409         SENF_LOG_CLASS_AREA();
410         typedef std::map<std::string, GenericNode::ptr> ChildMap;
411     public:
412         ///////////////////////////////////////////////////////////////////////////
413         // Types
414
415         typedef boost::shared_ptr<DirectoryNode> ptr;
416         typedef boost::shared_ptr<DirectoryNode const> cptr;
417         typedef boost::weak_ptr<DirectoryNode> weak_ptr;
418
419         typedef boost::iterator_range<ChildMap::const_iterator> ChildrenRange;
420         typedef ChildMap::const_iterator child_iterator;
421
422         typedef DirectoryNode node_type;
423         typedef DirectoryNode & return_type;
424
425         ///////////////////////////////////////////////////////////////////////////
426         ///\name Structors and default members
427         ///\{
428
429         static ptr create();            ///< Create node object.
430                                         /**< You should normally use either mkdir() or
431                                              ScopedDirectory instead of create() */
432
433         ///\}
434         ///////////////////////////////////////////////////////////////////////////
435         ///\name Children
436         ///\{
437
438         template <class NodeType>
439         NodeType & add(std::string const & name, boost::shared_ptr<NodeType> node);
440                                         ///< Add node to tree
441                                         /**< Adds the \a node to the tree as a child of \a this
442                                              node. The node is given the name \a name. If a node of
443                                              that name already exists, a numeric suffix of the form
444                                              '-n' is added to the name until the name is unique. If
445                                              \a name is empty, it is set to 'unnamed'. */
446
447         template <class Object>
448         typename NodeCreateTraits<Object>::result_type add(std::string const & name, 
449                                                            Object const & ob);
450                                         ///< Generic child node factory
451                                         /**< This member is used to create a new child node of the
452                                              current directory. The type of node created depends on
453                                              the type of argument passed.
454
455                                              The node type is selected by the NodeCreateTraits
456                                              class. To allow adding a specific node type, you need
457                                              to provide an overload for
458                                              <tt>senf_console_add_node</tt> which must be visible at
459                                              when you register the new node.
460                                              \code
461                                              MyNodeType & senf_console_add_node(
462                                                  DirectoryNode & dir,
463                                                  std::string const & name,
464                                                  MySpecialObject const & ob,
465                                                  int)
466                                              {
467                                                  return dir.add(name, MyNodeType::create(ob));
468                                              }
469                                              \endcode
470                                              (Do not forget the last unnamed 'int' parameter which
471                                              is not used but serves to disambiguate the
472                                              overloads). */
473
474         template <class Object>
475         typename NodeCreateTraits<Object>::result_type add(std::string const & name, 
476                                                            Object & ob);
477                                         ///< Generic child node factory
478                                         /**< \see add() */
479
480         GenericNode::ptr remove(std::string const & name);
481                                         ///< Remove node \a name from the tree
482                                         /**< The returned pointer may either be discarded, which
483                                              will automatically dispose the removed node, or it may
484                                              be saved and/or re-attached at some other place in the
485                                              tree. */
486
487         bool hasChild(std::string const & name) const;
488                                         ///< \c true, if there is a child with name \a name
489
490         GenericNode & get(std::string const & name) const;
491                                         ///< Get child node
492                                         /**< \throws UnknownNodeNameException if a child \a name
493                                                  does not exist */
494         GenericNode & getLink(std::string const & name) const;
495                                         ///< Get child node without dereferencing links
496                                         /**< \throws UnknownNodeNameException if a child \a name
497                                                  does not exist */
498
499         DirectoryNode & getDirectory(std::string const & name) const;
500                                         ///< Get directory child node
501                                         /**< Same as operator[]
502                                              \throws UnknownNodeNameException if a child \a name
503                                                  does not exist. 
504                                              \throws std::bad_cast if the child \a name is not a
505                                                  directory node. */
506         
507         DirectoryNode & operator[](std::string const & name) const;
508                                         ///< Get directory child node
509                                         /**< Same as getDirectory
510                                              \throws UnknownNodeNameException if a child \a name
511                                                  does not exist. 
512                                              \throws std::bad_cast if the child \a name is not a
513                                                  directory node. */
514
515         CommandNode & getCommand(std::string const & name) const;
516                                         ///< Get command child node
517                                         /**< Same as operator()
518                                              \throws UnknownNodeNameException if a child \a name
519                                                  does not exist
520                                              \throws std::bad_cast if the child \a name is not a
521                                                  command node. */
522
523         CommandNode & operator()(std::string const & name) const;
524                                         ///< Get command child node
525                                         /**< Same as getCommand()
526                                              \throws UnknownNodeNameException if a child \a name
527                                                  does not exist
528                                              \throws std::bad_cast if the child \a name is not a
529                                                  command node. */
530
531         DirectoryNode & mkdir(std::string const & name);
532                                         ///< Create sub-directory node
533         
534         ChildrenRange children() const; ///< Return iterator range over all children.
535                                         /**< The returned range is sorted by child name. */
536
537         ChildrenRange completions(std::string const & s) const;
538                                         ///< Return iterator range of completions for \a s
539                                         /**< The returned range is sorted by child name. */
540
541         void link(std::string const & name, GenericNode & target);
542
543         ///\}
544         ///////////////////////////////////////////////////////////////////////////
545
546         DirectoryNode & doc(std::string const & doc); ///< Set node documentation
547
548         ptr thisptr();
549         cptr thisptr() const;
550
551     protected:
552         DirectoryNode();
553
554     private:
555         void add(GenericNode::ptr node);
556         virtual void v_help(std::ostream & output) const;
557
558         ChildMap children_;
559         std::string doc_;
560
561         friend DirectoryNode & root();
562     };
563
564     /// Exception: Unknown node name
565     struct UnknownNodeNameException : public senf::Exception
566     { UnknownNodeNameException() : senf::Exception("Unknown node name") {}};
567
568 #ifndef DOXYGEN
569     template <class Type>
570     struct NodeCreateTraits< boost::shared_ptr<Type> >
571     {};
572 #endif
573
574     /** \brief Config/console tree command node
575
576         The CommandNode is the base-class for the tree leaf nodes. Concrete command node
577         implementations are derived from this class.
578
579         To execute a command, CommandNode::operator()() or CommandNode::execute() is called.
580
581         Subclass instances of this node type are automatically created when adding commands to the
582         tree. See \ref console_commands.
583
584         \ingroup node_tree
585       */
586     class CommandNode : public GenericNode
587     {
588         SENF_LOG_CLASS_AREA();
589     public:
590         ///////////////////////////////////////////////////////////////////////////
591         // Types
592
593         typedef boost::shared_ptr<CommandNode> ptr;
594         typedef boost::shared_ptr<CommandNode const> cptr;
595         typedef boost::weak_ptr<CommandNode> weak_ptr;
596
597         ///////////////////////////////////////////////////////////////////////////
598
599         void execute(std::ostream & output, ParseCommandInfo const & command) const;
600                                         ///< Execute the command
601                                         /**< Same as operator()()
602                                              \param[in] output stream where result messages may be
603                                                  written to
604                                              \param[in] arguments command arguments. This is a
605                                                  range of ranges of Token instances. */
606
607         void operator()(std::ostream & output, ParseCommandInfo const & command) const;
608                                         ///< Execute the command
609                                         /**< Same as execute()
610                                              \param[in] output stream where result messages may be
611                                                  written to
612                                              \param[in] arguments command arguments. This is a
613                                                  range of ranges of Token instances. */
614
615         ptr thisptr();
616         cptr thisptr() const;
617
618     protected:
619         CommandNode();
620
621 #ifndef DOXYGEN
622     private:
623 #endif
624         virtual void v_execute(std::ostream & output, ParseCommandInfo const & command) const = 0;
625                                         ///< Called to execute the command
626                                         /**< \param[in] output stream where result messages may be
627                                                  written to
628                                              \param[in] arguments command arguments. This is a
629                                                  range of ranges of Token instances. */
630
631     private:
632     };
633
634     /** \brief Most simple CommandNode implementation
635
636         This CommandNode implementation simply forwards the \a output and \a arguments arguments to
637         an arbitrary callback. Thus, it allows to add callbacks with the signature
638         \code
639         void callback(std::ostream & os, senf::console::ParseCommandInfo const & command)
640         { ... }
641         \endcode
642         to the tree.
643  
644         \ingroup console_commands
645      */
646     class SimpleCommandNode : public CommandNode
647     {
648         SENF_LOG_CLASS_AREA();
649     public:
650         ///////////////////////////////////////////////////////////////////////////
651         // Types
652
653         typedef boost::shared_ptr<SimpleCommandNode> ptr;
654         typedef boost::shared_ptr<SimpleCommandNode const> cptr;
655         typedef boost::weak_ptr<SimpleCommandNode> weak_ptr;
656
657         typedef boost::function<void (std::ostream &, ParseCommandInfo const &)> Function;
658
659         typedef SimpleCommandNode node_type;
660         typedef SimpleCommandNode & return_type;
661
662         ///////////////////////////////////////////////////////////////////////////
663         ///\name Structors and default members
664         ///\{
665
666         static ptr create(Function const & fn);
667
668         ///\}
669         ///////////////////////////////////////////////////////////////////////////
670
671         ptr thisptr();
672         cptr thisptr() const;
673
674         SimpleCommandNode & doc(std::string const & doc);
675
676     protected:
677         SimpleCommandNode(Function const & fn);
678
679     private:
680         virtual void v_help(std::ostream & output) const;
681         virtual void v_execute(std::ostream & output, ParseCommandInfo const & command) const;
682         
683
684         Function fn_;
685         std::string doc_;
686     };
687
688 #ifndef DOXYGEN
689
690     SimpleCommandNode & senf_console_add_node(DirectoryNode & node, std::string const & name, 
691                                               SimpleCommandNode::Function fn, int);
692
693 #endif
694
695 }}
696
697 #include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP()
698
699 BOOST_TYPEOF_REGISTER_TYPE(senf::console::DirectoryNode)
700 BOOST_TYPEOF_REGISTER_TYPE(senf::console::SimpleCommandNode)
701
702
703 ///////////////////////////////hh.e////////////////////////////////////////
704 #include "Node.cci"
705 //#include "Node.ct"
706 #include "Node.cti"
707 #endif
708
709 \f
710 // Local Variables:
711 // mode: c++
712 // fill-column: 100
713 // comment-column: 40
714 // c-file-style: "senf"
715 // indent-tabs-mode: nil
716 // ispell-local-dictionary: "american"
717 // compile-command: "scons -u test"
718 // End: