I can't think of anything simpler than trying to schedule a "for fun" project like cfperl. Obviously, there was no pressure to deliver on a certain date, and the requirements were entirely up to me. Why, then, did I come up with a schedule at all? A schedule forces programmers to think a certain way, to have a goal, to deliver a product. So I reluctantly forced a schedule upon myself for my own good. Of course, if you don't want to follow a schedule on a for-fun project, that's okay too. But then you run the very real risk of tweaking it forever without ever actually releasing it.
The schedule for release 1 of cfperl was as follows:
- December 2001 through January 2002: requirements
- January 2002 through February 2002: architecture
- February 2002 through July 2002: coding
- July 2002 through August 2002: debugging
- August 2002 through September 2002: documenting
The architecture of cfperl had to be similar to the architecture of
cfengine. I was, after all, trying to emulate the behavior of cfengine.
Thus, cfperl had to interpret cfengine configuration files, it had to take
at least some of the flags that cfengine takes, and it had to be very
flexible in its grammar to allow for changes in the cfengine grammar. I
decided to use the
Parse::RecDescent module because
speed was not of the
essence, and flexibility was very important. My experience with
Parse::RecDescent also pushed me in that direction. Hand crafting a
grammar in Perl instead of using
Parse::RecDescent is not a fun exercise
for any but the simplest cases. It seems so easy at the beginning, just a
while() loop...but then you find out about the comments and the white
spaces and how the grammar is different in each configuration section and
before you know it, your
while() loop is three pages long and doesn't fit
in your head anymore. Don't let your friends parse without a grammar.
My experience with other parsers, both hand-crafted ones, and modules like
Parse::Yapp, is that they work well for specific purposes, but
Parse::RecDescent offers the best integration with Perl. Also, the Perl 6
regular expressions and grammars will be very similar to
Parse::RecDescent's grammar, so porting cfperl to Perl 6 will be easy.
Parse::RecDescent I decided on having layered grammars, meaning that
there would be a top level parser (similar to the C precompiler), and the
top level parser would hand off specific sections to the parsers
responsible for those sections. For instance, the top level parser would
be responsible for including external files seamlessly into the
configuration, while the parser for the "classes" section would be
responsible for defining and undefining classes. I should mention that
cfengine has the concept of classes: runtime strings that may be defined
or undefined. For example, cfengine defines the class
Solaris if you're running on a Solaris machine.
Internally, cfperl stores defined and undefined classes in a Perl hash.
The value can be one (defined) or 0 (undefined). Classes are compounded
into complex logical statements:
Solaris.gemini will only be
true if running on a Solaris machine called gemini (or if the
has been otherwise defined).
that something should be executed only on a Solaris machine that is called
neither gemini nor jack (or if the
jack classes have not been
otherwise defined). I had to write a separate parser for these complex
logical statements. That was, perhaps, the hardest piece of cfperl I had
I used the
AppConfig module to handle command line switches like cfengine
does. This choice was based on my experience with
AppConfig, and on the
AppConfig's argument parsing and cfengine's argument
I will explain my other architecture choices as I take you through the cfperl project in the next few chapters.
In the next chapters, I will explain the exact features that cfengine did not provide, since those are the features that cfperl will be implementing. In brief, those are: user management, better file editing (the only cfengine feature I would improve in cfengine itself as well), crontab management, and dynamically executed Perl code.