1 ###############################################################################
3 # Package: NaturalDocs::ConfigFile
5 ###############################################################################
7 # A package to manage Natural Docs' configuration files.
11 # - Only one configuration file can be managed with this package at a time. You must close the file before opening another
14 ###############################################################################
16 # This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure
17 # Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
18 # Refer to License.txt for the complete details
23 package NaturalDocs::ConfigFile;
30 # All configuration files are text files.
34 # Comments start with the # character.
38 # All configuration files *must* have a format line as its first line containing content. Whitespace and comments are permitted
41 # > [keyword]: [value]
43 # Keywords can only contain <CFChars>. Keywords are not case sensitive. Values can be anything and run until the end of
44 # the line or a comment.
48 # Lines that don't start with a valid keyword format are considered to be all value.
50 # > [line] { [line] } [line]
52 # Files supporting brace groups (specified in <Open()>) may also have braces that can appear anywhere. It allows more than
53 # one thing to appear per line, which isn't supported otherwise. Consequently, values may not have braces.
60 # The characters that can appear in configuration file keywords and user-defined element names: letters, numbers, spaces,
61 # dashes, slashes, apostrophes, and periods.
63 # Although the list above is exhaustive, it should be noted that you especially can *not* use colons (messes up keyword: value
64 # sequences) commas (messes up item, item, item list sequences) and hashes (messes up comment detection.)
66 # You can search the source code for [CFChars] to find all the instances where this definition is used.
70 ###############################################################################
74 # handle: CONFIG_FILEHANDLE
76 # The file handle used for the configuration file.
83 # The <FileName> for the current configuration file being parsed.
91 # The <LineReader> used to read the configuration file.
99 # An array of errors added by <AddError()>. Every odd entry is the line number, and every even entry following is the
108 # The current line number for the configuration file.
114 # bool: hasBraceGroups
116 # Whether the file has brace groups or not.
122 # array: virtualLines
124 # An array of virtual lines if a line from the file contained more than one.
126 # Files with brace groups may have more than one virtual line per actual file line, such as "Group: A { Group: B". When that
127 # happens, any extra virtual lines are put into here so they can be returned on the next call.
133 ###############################################################################
140 # Opens a configuration file for parsing and returns the format <VersionInt>.
144 # file - The <FileName> to parse.
145 # hasBraceGroups - Whether the file supports brace groups or not. If so, lines with braces will be split apart behind the
150 # The <VersionInt> of the file, or undef if the file doesn't exist.
152 sub Open #(file, hasBraceGroups)
155 ($self, $file, $hasBraceGroups) = @_;
159 # It will be incremented to one when the first line is read from the file.
162 open(CONFIG_FILEHANDLE, '<' . $file) or return undef;
163 $lineReader = NaturalDocs::LineReader->New(\*CONFIG_FILEHANDLE);
166 # Get the format line.
168 my ($keyword, $value, $comment) = $self->GetLine();
170 if ($keyword eq 'format')
171 { return NaturalDocs::Version->FromString($value); }
173 { die "The first content line in " . $file . " must be the Format: line.\n"; };
180 # Closes the current configuration file.
185 close(CONFIG_FILEHANDLE);
192 # Returns the next line containing content, or an empty array if none.
196 # Returns the array ( keyword, value, comment ), or an empty array if none. All tabs will be converted to spaces, and all
197 # whitespace will be condensed into a single space.
199 # keyword - The keyword part of the line, if any. Is converted to lowercase and doesn't include the colon. If the file supports
200 # brace groups, opening and closing braces will be returned as keywords.
201 # value - The value part of the line, minus any whitespace. Keeps its original case.
202 # comment - The comment following the line, if any. This includes the # symbol and a leading space if there was
203 # any whitespace, since it may be significant. Otherwise undef. Used for lines where the # character needs to be
204 # accepted as part of the value.
210 my ($line, $comment);
213 # Get the next line with content.
221 if (scalar @virtualLines)
223 $line = shift @virtualLines;
228 $line = $lineReader->Get();
234 # Condense spaces and tabs into a single space.
240 # Split off the comment.
242 if ($line =~ /^(.*?)( ?#.*)$/)
243 { ($line, $comment) = ($1, $2); }
245 { $comment = undef; };
248 # Split any brace groups.
250 if ($isFileLine && $hasBraceGroups && $line =~ /[\{\}]/)
252 ($line, @virtualLines) = split(/([\{\}])/, $line);
254 $virtualLines[-1] .= $comment;
264 # We want to keep the leading space on a comment.
271 if ($hasBraceGroups && ($line eq '{' || $line eq '}'))
273 return ($line, undef, undef);
277 if ($line =~ /^([a-z0-9\ \'\/\.\-]+?) ?: ?(.*)$/i) # [CFChars]
279 my ($keyword, $value) = ($1, $2);
280 return (lc($keyword), $value, $comment);
285 return (undef, $line, $comment);
291 # Function: LineNumber
293 # Returns the line number for the line last returned by <GetLine()>.
296 { return $lineNumber; };
300 ###############################################################################
301 # Group: Error Functions
307 # Stores an error for the current configuration file. Will be attached to the last line read by <GetLine()>.
311 # message - The error message.
312 # lineNumber - The line number to use. If not specified, it will use the line number from the last call to <GetLine()>.
314 sub AddError #(message, lineNumber)
316 my ($self, $message, $messageLineNumber) = @_;
318 if (!defined $messageLineNumber)
319 { $messageLineNumber = $lineNumber; };
321 push @errors, $messageLineNumber, $message;
326 # Function: ErrorCount
328 # Returns how many errors the configuration file has.
332 return (scalar @errors) / 2;
337 # Function: PrintErrorsAndAnnotateFile
339 # Prints the errors to STDERR in the standard GNU format and annotates the configuration file with them. It does *not* end
340 # execution. <Close()> *must* be called before this function.
342 sub PrintErrorsAndAnnotateFile
348 open(CONFIG_FILEHANDLE, '<' . $file);
350 my $lineReader = NaturalDocs::LineReader->New(\*CONFIG_FILEHANDLE);
351 my @lines = $lineReader->GetAll();
353 close(CONFIG_FILEHANDLE);
355 # We need to keep track of both the real and the original line numbers. The original line numbers are for matching errors in
356 # the errors array, and don't include any comment lines added or deleted. Line number is the current line number including
357 # those comment lines for sending to the display.
359 my $originalLineNumber = 1;
361 open(CONFIG_FILEHANDLE, '>' . $file);
363 # We don't want to keep the old error header, if present.
364 if ($lines[0] =~ /^\# There (?:is an error|are \d+ errors) in this file\./)
367 $originalLineNumber++;
369 # We want to drop the blank line after it as well.
370 if ($lines[0] eq "\n")
373 $originalLineNumber++;
377 if ($self->ErrorCount() == 1)
379 print CONFIG_FILEHANDLE
380 "# There is an error in this file. Search for ERROR to find it.\n\n";
384 print CONFIG_FILEHANDLE
385 "# There are " . $self->ErrorCount() . " errors in this file. Search for ERROR to find them.\n\n";
391 foreach my $line (@lines)
393 while (scalar @errors && $originalLineNumber == $errors[0])
395 my $errorLine = shift @errors;
396 my $errorMessage = shift @errors;
398 print CONFIG_FILEHANDLE "# ERROR: " . $errorMessage . "\n";
400 # Use the GNU error format, which should make it easier to handle errors when Natural Docs is part of a build process.
401 # See http://www.gnu.org/prep/standards_15.html
403 $errorMessage = lcfirst($errorMessage);
404 $errorMessage =~ s/\.$//;
406 print STDERR 'NaturalDocs:' . $file . ':' . $lineNumber . ': ' . $errorMessage . "\n";
411 # We want to remove error lines from previous runs.
412 if (substr($line, 0, 9) ne '# ERROR: ')
414 print CONFIG_FILEHANDLE $line;
418 $originalLineNumber++;
421 # Clean up any remaining errors.
422 while (scalar @errors)
424 my $errorLine = shift @errors;
425 my $errorMessage = shift @errors;
427 print CONFIG_FILEHANDLE "# ERROR: " . $errorMessage . "\n";
429 # Use the GNU error format, which should make it easier to handle errors when Natural Docs is part of a build process.
430 # See http://www.gnu.org/prep/standards_15.html
432 $errorMessage = lcfirst($errorMessage);
433 $errorMessage =~ s/\.$//;
435 print STDERR 'NaturalDocs:' . $file . ':' . $lineNumber . ': ' . $errorMessage . "\n";
438 close(CONFIG_FILEHANDLE);
444 ###############################################################################
445 # Group: Misc Functions
449 # Function: HasOnlyCFChars
451 # Returns whether the passed string contains only <CFChars>.
453 sub HasOnlyCFChars #(string)
455 my ($self, $string) = @_;
456 return ($string =~ /^[a-z0-9\ \.\-\/\']*$/i); # [CFChars]
461 # Function: CFCharNames
463 # Returns a plain-english list of <CFChars> which can be embedded in a sentence. For example, "You can only use
464 # [CFCharsList()] in the name.
469 return 'letters, numbers, spaces, periods, dashes, slashes, and apostrophes';
476 # Obscures the passed text so that it is not user editable and returns it. The encoding method is not secure; it is just designed
477 # to be fast and to discourage user editing.
481 my ($self, $text) = @_;
483 # ` is specifically chosen to encode to space because of its rarity. We don't want a trailing one to get cut off before decoding.
484 $text =~ tr{a-zA-Z0-9\ \\\/\.\:\_\-\`}
485 {pY9fGc\`R8lAoE\\uIdH6tN\/7sQjKx0B5mW\.vZ41PyFg\:CrLaO\_eUi2DhT\-nSqJkXb3MwVz\ };
492 # Function: Unobscure
494 # Restores text encoded with <Obscure()> and returns it.
496 sub Unobscure #(text)
498 my ($self, $text) = @_;
500 $text =~ tr{pY9fGc\`R8lAoE\\uIdH6tN\/7sQjKx0B5mW\.vZ41PyFg\:CrLaO\_eUi2DhT\-nSqJkXb3MwVz\ }
501 {a-zA-Z0-9\ \\\/\.\:\_\-\`};