foundation-module: Chapter 3: Nowebify.
This commit is contained in:
parent
830118d644
commit
dce27ab9d8
8 changed files with 290 additions and 284 deletions
|
@ -7,36 +7,36 @@ code in this section provides a routine to carry out file opening as if
|
|||
filenames are case-insensitive, and is used only for extensions.
|
||||
|
||||
@ This section contains a single utility routine, contributed by Adam
|
||||
Thornton: a specialised, case-insensitive form of |fopen()|. It is specialised
|
||||
Thornton: a specialised, case-insensitive form of [[fopen()]]. It is specialised
|
||||
in that it is designed for opening extensions, where the file path will be
|
||||
case-correct up to the last two components of the path (the leafname and the
|
||||
immediately containing directory), but where the casing may be wrong in those
|
||||
last two components.
|
||||
|
||||
@ If the exact filename or extension directory (case-correct) exists,
|
||||
|CIFilingSystem::fopen()| will choose it to open. If not, it will
|
||||
use |strcasecmp()| to find a file or directory with the same name but
|
||||
[[CIFilingSystem::fopen()]] will choose it to open. If not, it will
|
||||
use [[strcasecmp()]] to find a file or directory with the same name but
|
||||
differing in case and use it instead. If it finds exactly one candidate file,
|
||||
it will then attempt to |fopen()| it and return the result.
|
||||
it will then attempt to [[fopen()]] it and return the result.
|
||||
|
||||
If |CIFilingSystem::fopen()| succeeds, it returns a |FILE *|
|
||||
(passed back to it from the underlying |fopen()|). If
|
||||
|CIFilingSystem::fopen()| fails, it returns |NULL|, and
|
||||
|errno| is set accordingly:
|
||||
(a) If no suitable file was found, |errno| is set to |ENOENT|.
|
||||
If [[CIFilingSystem::fopen()]] succeeds, it returns a [[FILE *]]
|
||||
(passed back to it from the underlying [[fopen()]]). If
|
||||
[[CIFilingSystem::fopen()]] fails, it returns [[NULL]], and
|
||||
[[errno]] is set accordingly:
|
||||
(a) If no suitable file was found, [[errno]] is set to [[ENOENT]].
|
||||
(b) If more than one possibility was found, but none of them exactly match
|
||||
the supplied case, |errno| is set to |EBADF|.
|
||||
the supplied case, [[errno]] is set to [[EBADF]].
|
||||
(c) Note that if multiple directories which match case-insensitively are
|
||||
found, but none is an exact match, |EBADF| will be set regardless of the
|
||||
found, but none is an exact match, [[EBADF]] will be set regardless of the
|
||||
contents of the directories.
|
||||
(d) If |CIFilingSystem::fopen()| fails during its allocation of
|
||||
(d) If [[CIFilingSystem::fopen()]] fails during its allocation of
|
||||
space to hold its intermediate strings for comparison, or for its various
|
||||
data structures, |errno| is set to |ENOMEM|.
|
||||
(e) If an unambiguous filename is found but the |fopen()| fails, |errno| is
|
||||
left at whatever value the underlying |fopen()| set it to.
|
||||
data structures, [[errno]] is set to [[ENOMEM]].
|
||||
(e) If an unambiguous filename is found but the [[fopen()]] fails, [[errno]] is
|
||||
left at whatever value the underlying [[fopen()]] set it to.
|
||||
|
||||
@h The routine. ^"ifdef-PLATFORM_POSIX"
|
||||
The routine is available only on POSIX platforms where |PLATFORM_POSIX|
|
||||
@ \section{The routine. ^"ifdef-PLATFORM_POSIX"}
|
||||
The routine is available only on POSIX platforms where [[PLATFORM_POSIX]]
|
||||
is defined (see "Platform-Specific Definitions"). In practice this means
|
||||
everywhere except Windows, but all Windows file systems are case-preserving
|
||||
and case-insensitive in any case.
|
||||
|
@ -46,7 +46,7 @@ for the given casing, then if that fails, for a unique alternative with
|
|||
different casing; and then repeat within that directory for the extension
|
||||
file itself.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
FILE *CIFilingSystem::fopen(const char *path, const char *mode) {
|
||||
char *topdirpath = NULL, *ciextdirpath = NULL, *cistring = NULL, *ciextname = NULL;
|
||||
char *workstring = NULL, *workstring2 = NULL;
|
||||
|
@ -54,96 +54,96 @@ FILE *CIFilingSystem::fopen(const char *path, const char *mode) {
|
|||
size_t length;
|
||||
|
||||
/* for efficiency's sake, though it's logically equivalent, we try... */
|
||||
handle = fopen(path, mode); if (handle) @<Happy ending to ci-fopen@>;
|
||||
handle = fopen(path, mode); if (handle) <<Happy ending to ci-fopen>>;
|
||||
|
||||
@<Find the length of the path, giving an error if it is empty or NULL@>;
|
||||
@<Allocate memory for strings large enough to hold any subpath of the path@>;
|
||||
@<Parse the path to break it into topdir path, extension directory and leafname@>;
|
||||
<<Find the length of the path, giving an error if it is empty or NULL>>;
|
||||
<<Allocate memory for strings large enough to hold any subpath of the path>>;
|
||||
<<Parse the path to break it into topdir path, extension directory and leafname>>;
|
||||
|
||||
topdir = opendir(topdirpath); /* whose pathname is assumed case-correct... */
|
||||
if (topdir == NULL) @<Sad ending to ci-fopen@>; /* ...so that failure is fatal; |errno| is set by |opendir| */
|
||||
if (topdir == NULL) <<Sad ending to ci-fopen>>; /* ...so that failure is fatal; [[errno| is set by |opendir]] */
|
||||
|
||||
sprintf(workstring, "%s%c%s", topdirpath, FOLDER_SEPARATOR, ciextdirpath);
|
||||
extdir = opendir(workstring); /* try with supplied extension directory name */
|
||||
if (extdir == NULL) @<Try to find a unique insensitively matching directory name in topdir@>
|
||||
if (extdir == NULL) <<Try to find a unique insensitively matching directory name in topdir>>
|
||||
else strcpy(cistring, workstring);
|
||||
|
||||
sprintf(workstring, "%s%c%s", cistring, FOLDER_SEPARATOR, ciextname);
|
||||
handle = fopen(workstring, mode); /* try with supplied name */
|
||||
if (handle) @<Happy ending to ci-fopen@>;
|
||||
if (handle) <<Happy ending to ci-fopen>>;
|
||||
|
||||
@<Try to find a unique insensitively matching entry in extdir@>;
|
||||
<<Try to find a unique insensitively matching entry in extdir>>;
|
||||
}
|
||||
|
||||
@h Looking for case-insensitive matches instead.
|
||||
@ \section{Looking for case-insensitive matches instead.}
|
||||
We emerge from the following only in the happy case where a unique matching
|
||||
directory name can be found.
|
||||
|
||||
@<Try to find a unique insensitively matching directory name in topdir@> =
|
||||
<<Try to find a unique insensitively matching directory name in topdir>>=
|
||||
int rc = CIFilingSystem::match_in_directory(topdir, ciextdirpath, workstring);
|
||||
switch (rc) {
|
||||
case 0:
|
||||
errno = ENOENT; @<Sad ending to ci-fopen@>;
|
||||
errno = ENOENT; <<Sad ending to ci-fopen>>;
|
||||
case 1:
|
||||
sprintf(cistring, "%s%c%s", topdirpath, FOLDER_SEPARATOR, workstring);
|
||||
extdir = opendir(cistring);
|
||||
if (extdir == NULL) {
|
||||
errno = ENOENT; @<Sad ending to ci-fopen@>;
|
||||
errno = ENOENT; <<Sad ending to ci-fopen>>;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
errno = EBADF; @<Sad ending to ci-fopen@>;
|
||||
errno = EBADF; <<Sad ending to ci-fopen>>;
|
||||
}
|
||||
|
||||
@ More or less the same, but we never emerge at all: all cases of the switch
|
||||
return from the function.
|
||||
|
||||
@<Try to find a unique insensitively matching entry in extdir@> =
|
||||
<<Try to find a unique insensitively matching entry in extdir>>=
|
||||
int rc = CIFilingSystem::match_in_directory(extdir, ciextname, workstring);
|
||||
|
||||
switch (rc) {
|
||||
case 0:
|
||||
errno = ENOENT; @<Sad ending to ci-fopen@>;
|
||||
errno = ENOENT; <<Sad ending to ci-fopen>>;
|
||||
case 1:
|
||||
sprintf(workstring2, "%s%c%s", cistring, FOLDER_SEPARATOR, workstring);
|
||||
workstring2[length] = 0;
|
||||
handle = fopen(workstring2, mode);
|
||||
if (handle) @<Happy ending to ci-fopen@>;
|
||||
errno = ENOENT; @<Sad ending to ci-fopen@>;
|
||||
if (handle) <<Happy ending to ci-fopen>>;
|
||||
errno = ENOENT; <<Sad ending to ci-fopen>>;
|
||||
default:
|
||||
errno = EBADF; @<Sad ending to ci-fopen@>;
|
||||
errno = EBADF; <<Sad ending to ci-fopen>>;
|
||||
}
|
||||
|
||||
@h Allocation and deallocation.
|
||||
@ \section{Allocation and deallocation.}
|
||||
We use six strings to hold full or partial pathnames.
|
||||
|
||||
@<Allocate memory for strings large enough to hold any subpath of the path@> =
|
||||
<<Allocate memory for strings large enough to hold any subpath of the path>>=
|
||||
workstring = calloc(length+1, sizeof(char));
|
||||
if (workstring == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
|
||||
if (workstring == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
|
||||
workstring2 = calloc(length+1, sizeof(char));
|
||||
if (workstring2 == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
|
||||
if (workstring2 == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
|
||||
topdirpath = calloc(length+1, sizeof(char));
|
||||
if (topdirpath == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
|
||||
if (topdirpath == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
|
||||
ciextdirpath = calloc(length+1, sizeof(char));
|
||||
if (ciextdirpath == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
|
||||
if (ciextdirpath == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
|
||||
cistring = calloc(length+1, sizeof(char));
|
||||
if (cistring == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
|
||||
if (cistring == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
|
||||
ciextname = calloc(length+1, sizeof(char));
|
||||
if (ciextname == NULL) { errno = ENOMEM; @<Sad ending to ci-fopen@>; }
|
||||
if (ciextname == NULL) { errno = ENOMEM; <<Sad ending to ci-fopen>>; }
|
||||
|
||||
@ If we are successful, we return a valid file handle...
|
||||
|
||||
@<Happy ending to ci-fopen@> =
|
||||
@<Prepare to exit ci-fopen cleanly@>;
|
||||
<<Happy ending to ci-fopen>>=
|
||||
<<Prepare to exit ci-fopen cleanly>>;
|
||||
return handle;
|
||||
|
||||
@ ...and otherwise |NULL|, having already set |errno| with the reason why.
|
||||
@ ...and otherwise [[NULL]], having already set [[errno]] with the reason why.
|
||||
|
||||
@<Sad ending to ci-fopen@> =
|
||||
@<Prepare to exit ci-fopen cleanly@>;
|
||||
<<Sad ending to ci-fopen>>=
|
||||
<<Prepare to exit ci-fopen cleanly>>;
|
||||
return NULL;
|
||||
|
||||
@<Prepare to exit ci-fopen cleanly@> =
|
||||
<<Prepare to exit ci-fopen cleanly>>=
|
||||
if (workstring) free(workstring);
|
||||
if (workstring2) free(workstring2);
|
||||
if (topdirpath) free(topdirpath);
|
||||
|
@ -153,25 +153,25 @@ We use six strings to hold full or partial pathnames.
|
|||
if (topdir) closedir(topdir);
|
||||
if (extdir) closedir(extdir);
|
||||
|
||||
@h Pathname hacking.
|
||||
@ \section{Pathname hacking.}
|
||||
|
||||
@<Find the length of the path, giving an error if it is empty or NULL@> =
|
||||
<<Find the length of the path, giving an error if it is empty or NULL>>=
|
||||
length = 0;
|
||||
if (path) length = (size_t) strlen(path);
|
||||
if (length < 1) { errno = ENOENT; return NULL; }
|
||||
|
||||
@ And here we break up a pathname like
|
||||
= (text)
|
||||
|
||||
/Users/bobama/Library/Inform/Extensions/Hillary Clinton/Health Care.i7x
|
||||
=
|
||||
|
||||
into three components:
|
||||
(a) |topdirpath| is |/Users/bobama/Library/Inform/Extensions|, and its casing is correct.
|
||||
(b) |ciextdirpath| is |Hillary Clinton|, but its casing may not be correct.
|
||||
(c) |ciextname| is |Health Care.i7x|, but its casing may not be correct.
|
||||
(a) [[topdirpath]] is [[/Users/bobama/Library/Inform/Extensions]], and its casing is correct.
|
||||
(b) [[ciextdirpath]] is [[Hillary Clinton]], but its casing may not be correct.
|
||||
(c) [[ciextname]] is [[Health Care.i7x]], but its casing may not be correct.
|
||||
|
||||
The contents of |workstring| are not significant afterwards.
|
||||
The contents of [[workstring]] are not significant afterwards.
|
||||
|
||||
@<Parse the path to break it into topdir path, extension directory and leafname@> =
|
||||
<<Parse the path to break it into topdir path, extension directory and leafname>>=
|
||||
char *p;
|
||||
size_t extdirindex = 0, extindex = 0, namelen = 0, dirlen = 0;
|
||||
|
||||
|
@ -197,11 +197,11 @@ The contents of |workstring| are not significant afterwards.
|
|||
strncpy(ciextdirpath, path + extdirindex + 1, dirlen);
|
||||
ciextdirpath[dirlen] = 0;
|
||||
|
||||
@h strrchr.
|
||||
@ \section{strrchr.}
|
||||
This is an elderly C library function, really, but rewritten so that it
|
||||
can recognise any folder separator character.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
char *CIFilingSystem::strrchr(const char *p) {
|
||||
const char *q = NULL;
|
||||
while (*p) {
|
||||
|
@ -211,17 +211,17 @@ char *CIFilingSystem::strrchr(const char *p) {
|
|||
return (char *) q;
|
||||
}
|
||||
|
||||
@h Counting matches.
|
||||
@ \section{Counting matches.}
|
||||
We count the number of names within the directory which case-insensitively
|
||||
match against |name|, and copy the last which matches into |last_match|.
|
||||
This must be at least as long as |name|. (We ought to be just a little careful
|
||||
match against [[name]], and copy the last which matches into [[last_match]].
|
||||
This must be at least as long as [[name]]. (We ought to be just a little careful
|
||||
in case of improbable cases where the matched name contains a different
|
||||
number of characters from |name|, for instance because on a strict reading
|
||||
number of characters from [[name]], for instance because on a strict reading
|
||||
of Unicode "SS" is casing-equivalent to the eszet, but it's unlikely
|
||||
that many contemporary implementations of |strcasecmp| are aware of this,
|
||||
that many contemporary implementations of [[strcasecmp]] are aware of this,
|
||||
and in any case the code above contains much larger buffers than needed.)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int CIFilingSystem::match_in_directory(void *vd,
|
||||
char *name, char *last_match) {
|
||||
DIR *d = (DIR *) vd;
|
||||
|
@ -238,10 +238,10 @@ int CIFilingSystem::match_in_directory(void *vd,
|
|||
return rc;
|
||||
}
|
||||
|
||||
@h Non-POSIX tail. ^"ifndef-PLATFORM_POSIX"
|
||||
On platforms without POSIX directory handling, we revert to regular |fopen|.
|
||||
@ \section{Non-POSIX tail. ^"ifndef-PLATFORM_POSIX"}
|
||||
On platforms without POSIX directory handling, we revert to regular [[fopen]].
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
FILE *CIFilingSystem::fopen(const char *path, const char *mode) {
|
||||
return fopen(path, mode);
|
||||
}
|
|
@ -3,63 +3,65 @@
|
|||
To parse the command line arguments with which inweb was called,
|
||||
and to handle any errors it needs to issue.
|
||||
|
||||
@h Model.
|
||||
@ \section{Model.}
|
||||
Our scheme is that the command line syntax will contain an optional
|
||||
series of dashed switches. Some switches appear alone, others must be
|
||||
followed by an argument. Anything not part of the switches is termed
|
||||
a "bareword". For example, in
|
||||
= (text)
|
||||
|
||||
-log no-memory-usage -fixtime jam marmalade
|
||||
=
|
||||
there are two switches, |-log| taking an argument (it has valency 2
|
||||
in the terminology below), |-fixtime| not (valency 1). There are
|
||||
then two barewords, |jam| and |marmalade|.
|
||||
|
||||
there are two switches, [[-log]] taking an argument (it has valency 2
|
||||
in the terminology below), [[-fixtime]] not (valency 1). There are
|
||||
then two barewords, [[jam]] and [[marmalade]].
|
||||
|
||||
For an example of all this in action, see Inweb, or see the basic
|
||||
command-line switches created by Foundation itself in "Foundation".
|
||||
|
||||
@h Switches.
|
||||
@ \section{Switches.}
|
||||
Each different switch available is stored in one of these structures.
|
||||
Switches come in five sorts:
|
||||
|
||||
@e ACTION_CLSF from 1 /* does something */
|
||||
@e BOOLEAN_ON_CLSF /* sets a flag true */
|
||||
@e BOOLEAN_OFF_CLSF /* sets a flag false */
|
||||
@e NUMERICAL_CLSF /* sets an integer to the given value */
|
||||
@e TEXTUAL_CLSF /* sets text to the given value */
|
||||
<<*>>=
|
||||
enum ACTION_CLSF from 1 /* does something */
|
||||
enum BOOLEAN_ON_CLSF /* sets a flag true */
|
||||
enum BOOLEAN_OFF_CLSF /* sets a flag false */
|
||||
enum NUMERICAL_CLSF /* sets an integer to the given value */
|
||||
enum TEXTUAL_CLSF /* sets text to the given value */
|
||||
|
||||
@ Switches are also grouped, though this affects only the printout of them
|
||||
in |-help|. Groups are enumerated thus:
|
||||
in [[-help]]. Groups are enumerated thus:
|
||||
|
||||
@e NO_CLSG from 0
|
||||
@e FOUNDATION_CLSG
|
||||
<<*>>=
|
||||
enum NO_CLSG from 0
|
||||
enum FOUNDATION_CLSG
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct command_line_switch {
|
||||
int switch_id;
|
||||
struct text_stream *switch_name; /* e.g., |no-verbose| */
|
||||
struct text_stream *switch_sort_name; /* e.g., |verbose| */
|
||||
struct text_stream *switch_name; /* e.g., [[no-verbose]] */
|
||||
struct text_stream *switch_sort_name; /* e.g., [[verbose]] */
|
||||
struct text_stream *help_text;
|
||||
int valency; /* 1 for bare, 2 for one argument follows */
|
||||
int form; /* one of the |*_CLSF| values above */
|
||||
int switch_group; /* one of the |*_CLSG| valyes above */
|
||||
int form; /* one of the [[*_CLSF]] values above */
|
||||
int switch_group; /* one of the [[*_CLSG]] valyes above */
|
||||
int active_by_default; /* relevant only for booleans */
|
||||
struct command_line_switch *negates; /* relevant only for booleans */
|
||||
CLASS_DEFINITION
|
||||
} command_line_switch;
|
||||
|
||||
@ In case of a prodigious number of switches (ever tried typing |clang -help|?),
|
||||
@ In case of a prodigious number of switches (ever tried typing [[clang -help]]?),
|
||||
we'll hash the switch names into the following:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
dictionary *cls_dictionary = NULL;
|
||||
|
||||
@ The client must declare all the switches her program will make use of, not
|
||||
counting the standard set already declared by Foundation (such as |-help|).
|
||||
A new |*_CLSW| value should be enumerated to be the ID referring to this
|
||||
counting the standard set already declared by Foundation (such as [[-help]]).
|
||||
A new [[*_CLSW]] value should be enumerated to be the ID referring to this
|
||||
swtich, and then the client should call:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int current_switch_group = -1;
|
||||
text_stream *switch_group_names[NO_DEFINED_CLSG_VALUES+1];
|
||||
void CommandLine::begin_group(int id, text_stream *name) {
|
||||
|
@ -86,7 +88,7 @@ command_line_switch *CommandLine::declare_switch_p(int id,
|
|||
if (cls_dictionary == NULL) cls_dictionary = Dictionaries::new(16, FALSE);
|
||||
command_line_switch *cls = CREATE(command_line_switch);
|
||||
cls->switch_name = name;
|
||||
@<Make the sorting name@>;
|
||||
<<Make the sorting name>>;
|
||||
cls->switch_id = id;
|
||||
cls->valency = val;
|
||||
cls->help_text = help_literal;
|
||||
|
@ -99,21 +101,21 @@ command_line_switch *CommandLine::declare_switch_p(int id,
|
|||
return cls;
|
||||
}
|
||||
|
||||
@ When we alphabetically sort switches for the |-help| output, we want to
|
||||
file, say, |-no-verbose| immediately after |-verbose|, not back in the N
|
||||
section. So the sorting version of |no-verbose| is |verbose_|.
|
||||
@ When we alphabetically sort switches for the [[-help]] output, we want to
|
||||
file, say, [[-no-verbose]] immediately after [[-verbose]], not back in the N
|
||||
section. So the sorting version of [[no-verbose]] is [[verbose_]].
|
||||
|
||||
@<Make the sorting name@> =
|
||||
<<Make the sorting name>>=
|
||||
cls->switch_sort_name = Str::duplicate(name);
|
||||
if (Str::begins_with_wide_string(name, L"no-")) {
|
||||
Str::delete_n_characters(cls->switch_sort_name, 3);
|
||||
WRITE_TO(cls->switch_sort_name, "_");
|
||||
}
|
||||
|
||||
@ Booleans are automatically created in pairs, e.g., |-destroy-world| and
|
||||
|-no-destroy-world|:
|
||||
@ Booleans are automatically created in pairs, e.g., [[-destroy-world]] and
|
||||
[[-no-destroy-world]]:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
command_line_switch *CommandLine::declare_boolean_switch(int id,
|
||||
wchar_t *name_literal, int val, wchar_t *help_literal, int active) {
|
||||
command_line_switch *cls =
|
||||
|
@ -147,17 +149,17 @@ void CommandLine::declare_textual_switch(int id,
|
|||
cls->form = TEXTUAL_CLSF;
|
||||
}
|
||||
|
||||
@h Reading the command line.
|
||||
@ \section{Reading the command line.}
|
||||
Once all the switches are declared, the client calls the following routine
|
||||
in order to parse the usual C |argc| and |argv| pair, and take action as
|
||||
appropriate. The client passes a pointer to some structure in |state|:
|
||||
in order to parse the usual C [[argc]] and [[argv]] pair, and take action as
|
||||
appropriate. The client passes a pointer to some structure in [[state]]:
|
||||
probably a structure holding its settings variables. When we parse a
|
||||
switch, we call |f| to say so; when we parse a bareword, we call |g|. In
|
||||
each case we pass back |state| so that these functions can record whatever
|
||||
switch, we call [[f]] to say so; when we parse a bareword, we call [[g]]. In
|
||||
each case we pass back [[state]] so that these functions can record whatever
|
||||
they would like to in the state structure.
|
||||
|
||||
The return value is |TRUE| if the command line appeared to contain at least
|
||||
one non-trivial request, but |FALSE| if it only asked for e.g. |-help|. In
|
||||
The return value is [[TRUE]] if the command line appeared to contain at least
|
||||
one non-trivial request, but [[FALSE]] if it only asked for e.g. [[-help]]. In
|
||||
general, the client should then exit with exit code 0 if this happens.
|
||||
|
||||
This is all easier to demonstrate than explain. See Inweb for an example.
|
||||
|
@ -165,9 +167,10 @@ This is all easier to demonstrate than explain. See Inweb for an example.
|
|||
@ Here goes the reader. It works through the command line arguments, then
|
||||
through the file if one has by that point been provided.
|
||||
|
||||
@d BOGUS_CLSN -12345678 /* bogus because guaranteed not to be a genuine switch ID */
|
||||
<<*>>=
|
||||
#define BOGUS_CLSN -12345678 /* bogus because guaranteed not to be a genuine switch ID */
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct clf_reader_state {
|
||||
void *state;
|
||||
void (*f)(int, int, text_stream *, void *);
|
||||
|
@ -220,7 +223,7 @@ void CommandLine::read_array(clf_reader_state *crs, int argc, char **argv) {
|
|||
@ We can also read the "command line" from a file. The following variable
|
||||
holds the filename to read from.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
filename *command_line_file = NULL;
|
||||
void CommandLine::also_read_file(filename *F) {
|
||||
command_line_file = F;
|
||||
|
@ -231,7 +234,7 @@ from the debugging log what switches were actually used. But since the log
|
|||
might not exist as early as now, we have to record any log entries, and play
|
||||
them back later (i.e., when the debugging log does exist).
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
linked_list *command_line_logs = NULL;
|
||||
void CommandLine::record_log(text_stream *line) {
|
||||
if (command_line_logs == NULL)
|
||||
|
@ -248,14 +251,14 @@ void CommandLine::play_back_log(void) {
|
|||
}
|
||||
|
||||
@ White space at start and end of lines is ignored; blank lines and those
|
||||
beginning with a |#| are ignored (but a # following other content does not
|
||||
beginning with a [[#]] are ignored (but a # following other content does not
|
||||
mean a comment, so don't use trailing comments on lines); each line must
|
||||
either be a single switch like |-no-service| or a pair like |-connect tower11|.
|
||||
Shell conventions on quoting are not used, but the line |-greet Fred Smith|
|
||||
is equivalent to |-greet 'Fred Smith'| on the command line, so there's no
|
||||
either be a single switch like [[-no-service]] or a pair like [[-connect tower11]].
|
||||
Shell conventions on quoting are not used, but the line [[-greet Fred Smith]]
|
||||
is equivalent to [[-greet 'Fred Smith']] on the command line, so there's no
|
||||
problem with internal space characters in arguments.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void CommandLine::read_file(clf_reader_state *crs) {
|
||||
text_stream *logline = Str::new();
|
||||
WRITE_TO(logline, "Reading further switches from file: %f", command_line_file);
|
||||
|
@ -271,7 +274,7 @@ void CommandLine::read_file(clf_reader_state *crs) {
|
|||
void CommandLine::read_file_helper(text_stream *text, text_file_position *tfp, void *state) {
|
||||
clf_reader_state *crs = (clf_reader_state *) state;
|
||||
match_results mr = Regexp::create_mr();
|
||||
if ((Str::is_whitespace(text)) || (Regexp::match(&mr, text, L" *#%c*"))) {
|
||||
if ((Str::is_whitespace(text)) [[]] (Regexp::match(&mr, text, L" *#%c*"))) {
|
||||
;
|
||||
} else {
|
||||
text_stream *logline = Str::new();
|
||||
|
@ -302,16 +305,16 @@ void CommandLine::read_one(clf_reader_state *crs, text_stream *opt) {
|
|||
crs->subs = TRUE;
|
||||
}
|
||||
|
||||
@ We also allow |-setting=X| as equivalent to |-setting X|.
|
||||
@ We also allow [[-setting=X]] as equivalent to [[-setting X]].
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int CommandLine::read_pair(clf_reader_state *crs, text_stream *opt, text_stream *arg) {
|
||||
TEMPORARY_TEXT(opt_p)
|
||||
TEMPORARY_TEXT(opt_val)
|
||||
Str::copy(opt_p, opt);
|
||||
int N = BOGUS_CLSN;
|
||||
match_results mr = Regexp::create_mr();
|
||||
if ((Regexp::match(&mr, opt, L"(%c+)=(%d+)")) ||
|
||||
if ((Regexp::match(&mr, opt, L"(%c+)=(%d+)")) [[]]
|
||||
(Regexp::match(&mr, opt, L"(%c+)=(-%d+)"))) {
|
||||
N = Str::atoi(mr.exp[1], 0);
|
||||
Str::copy(opt_p, mr.exp[0]);
|
||||
|
@ -328,7 +331,7 @@ int CommandLine::read_pair(clf_reader_state *crs, text_stream *opt, text_stream
|
|||
|
||||
@ So at this point we have definitely found what looks like a switch:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int CommandLine::read_pair_p(text_stream *opt, text_stream *opt_val, int N,
|
||||
text_stream *arg, void *state,
|
||||
void (*f)(int, int, text_stream *, void *), int *substantive) {
|
||||
|
@ -350,22 +353,22 @@ int CommandLine::read_pair_p(text_stream *opt, text_stream *opt_val, int N,
|
|||
}
|
||||
}
|
||||
int innocuous = FALSE;
|
||||
@<Take action on what is now definitely a switch@>;
|
||||
<<Take action on what is now definitely a switch>>;
|
||||
if ((innocuous == FALSE) && (substantive)) *substantive = TRUE;
|
||||
return cls->valency;
|
||||
}
|
||||
|
||||
@ The common set of switches declared by Foundation are all handled here;
|
||||
all other switches are delegated to the client's callback function |f|.
|
||||
all other switches are delegated to the client's callback function [[f]].
|
||||
|
||||
@<Take action on what is now definitely a switch@> =
|
||||
<<Take action on what is now definitely a switch>>=
|
||||
switch (cls->switch_id) {
|
||||
case CRASH_CLSW:
|
||||
if (cls->form == BOOLEAN_ON_CLSF) {
|
||||
Errors::enter_debugger_mode(); innocuous = TRUE;
|
||||
}
|
||||
break;
|
||||
case LOG_CLSW: @<Parse debugging log inclusion@>; innocuous = TRUE; break;
|
||||
case LOG_CLSW: <<Parse debugging log inclusion>>; innocuous = TRUE; break;
|
||||
case VERSION_CLSW: {
|
||||
PRINT("[[Title]]");
|
||||
char *svn = "[[Semantic Version Number]]";
|
||||
|
@ -398,7 +401,7 @@ all other switches are delegated to the client's callback function |f|.
|
|||
break;
|
||||
}
|
||||
|
||||
@<Parse debugging log inclusion@> =
|
||||
<<Parse debugging log inclusion>>=
|
||||
if (Log::get_debug_log_filename() == NULL) {
|
||||
TEMPORARY_TEXT(itn)
|
||||
WRITE_TO(itn, "%s", PROGRAM_NAME);
|
||||
|
@ -409,8 +412,8 @@ all other switches are delegated to the client's callback function |f|.
|
|||
Log::open();
|
||||
Log::set_aspect_from_command_line(arg, TRUE);
|
||||
|
||||
@h Help text.
|
||||
That just leaves the following, which implements the |-help| switch. It
|
||||
@ \section{Help text.}
|
||||
That just leaves the following, which implements the [[-help]] switch. It
|
||||
alphabetically sorts the switches, and prints out a list of them as grouped,
|
||||
with ungrouped switches as the top paragraph and Foundation switches as the
|
||||
bottom one. (Those are the dull ones.)
|
||||
|
@ -418,7 +421,7 @@ bottom one. (Those are the dull ones.)
|
|||
If a header text has been declared, that appears above the list. It's usually
|
||||
a brief description of the tool's name and purpose.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
text_stream *cls_heading = NULL;
|
||||
|
||||
void CommandLine::declare_heading(wchar_t *heading_text_literal) {
|
||||
|
@ -440,17 +443,17 @@ void CommandLine::write_help(OUTPUT_STREAM) {
|
|||
|
||||
if (Str::len(cls_heading) > 0) WRITE("%S\n", cls_heading);
|
||||
int filter = NO_CLSG, new_para_needed = FALSE;
|
||||
@<Show options in alphabetical order@>;
|
||||
<<Show options in alphabetical order>>;
|
||||
for (filter = NO_CLSG; filter<NO_DEFINED_CLSG_VALUES; filter++)
|
||||
if ((filter != NO_CLSG) && (filter != FOUNDATION_CLSG))
|
||||
@<Show options in alphabetical order@>;
|
||||
<<Show options in alphabetical order>>;
|
||||
filter = FOUNDATION_CLSG;
|
||||
@<Show options in alphabetical order@>;
|
||||
<<Show options in alphabetical order>>;
|
||||
|
||||
Memory::I7_free(sorted_table, ARRAY_SORTING_MREASON, N*((int) sizeof(command_line_switch *)));
|
||||
}
|
||||
|
||||
@<Show options in alphabetical order@> =
|
||||
<<Show options in alphabetical order>>=
|
||||
if (new_para_needed) {
|
||||
WRITE("\n");
|
||||
new_para_needed = FALSE;
|
||||
|
@ -458,7 +461,7 @@ void CommandLine::write_help(OUTPUT_STREAM) {
|
|||
for (int i=0; i<N; i++) {
|
||||
command_line_switch *cls = sorted_table[i];
|
||||
if (cls->switch_group != filter) continue;
|
||||
if ((cls->form == BOOLEAN_OFF_CLSF) || (cls->form == BOOLEAN_ON_CLSF)) {
|
||||
if ((cls->form == BOOLEAN_OFF_CLSF) [[]] (cls->form == BOOLEAN_ON_CLSF)) {
|
||||
if (cls->active_by_default) continue;
|
||||
}
|
||||
text_stream *label = switch_group_names[filter];
|
||||
|
@ -482,7 +485,7 @@ void CommandLine::write_help(OUTPUT_STREAM) {
|
|||
DISCARD_TEXT(line)
|
||||
}
|
||||
|
||||
@ =
|
||||
<<*>>=
|
||||
int CommandLine::compare_names(const void *ent1, const void *ent2) {
|
||||
text_stream *tx1 = (*((const command_line_switch **) ent1))->switch_sort_name;
|
||||
text_stream *tx2 = (*((const command_line_switch **) ent2))->switch_sort_name;
|
|
@ -4,7 +4,7 @@ Scanning directories on the host filing system.
|
|||
|
||||
@ All of this abstracts the code already found in the platform definitions.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct scan_directory {
|
||||
void *directory_handle;
|
||||
char directory_name_written_out[4*MAX_FILENAME_LENGTH];
|
||||
|
@ -15,7 +15,7 @@ typedef struct scan_directory {
|
|||
whatever the locale encoding is; the filenames coming back have to be
|
||||
transcoded the other way.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
scan_directory *Directories::open_from(text_stream *name) {
|
||||
scan_directory *D = CREATE(scan_directory);
|
||||
Str::copy_to_locale_string(D->directory_name_written_out, name, 4*MAX_FILENAME_LENGTH);
|
||||
|
@ -51,7 +51,7 @@ void Directories::close(scan_directory *D) {
|
|||
@ Incredibly, this seems to be the most portable method for testing whether a
|
||||
directory exists in the file system:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Directories::exists(pathname *P) {
|
||||
scan_directory *TRY = Directories::open(P);
|
||||
if (TRY == NULL) return FALSE;
|
||||
|
@ -60,7 +60,7 @@ int Directories::exists(pathname *P) {
|
|||
}
|
||||
|
||||
@ It turns out to be useful to scan the contents of a directory in an order
|
||||
which is predictable regardless of platform -- |Platform::readdir| works in a
|
||||
which is predictable regardless of platform -- [[Platform::readdir]] works in a
|
||||
different order on MacOS, Windows and Linux, even given the same directory
|
||||
of files to work on. So the following returns a linked list of the contents,
|
||||
sorted into alphabetical order, but case-insensitively. For the Inform project,
|
||||
|
@ -70,7 +70,7 @@ in casing, so this ordering is effectively deterministic.
|
|||
There's some time and memory overhead here, but unless we're dealing with
|
||||
directories holding upwards of 10,000 files or so, it'll be trivial.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
linked_list *Directories::listing(pathname *P) {
|
||||
int capacity = 4, used = 0;
|
||||
text_stream **listing_array = (text_stream **)
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
A basic system for command-line tool error messages.
|
||||
|
||||
@h Errors handler.
|
||||
@ \section{Errors handler.}
|
||||
The user can provide a routine to deal with error messages before they're
|
||||
issued. If this returns |FALSE|, nothing is printed to |stderr|.
|
||||
issued. If this returns [[FALSE]], nothing is printed to [[stderr]].
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int (*errors_handler)(text_stream *, int) = NULL;
|
||||
void (*internal_errors_handler)(void *, char *, char *, int) = NULL;
|
||||
|
||||
|
@ -32,14 +32,14 @@ void Errors::issue(text_stream *message, int fatality) {
|
|||
if (fatality) Errors::die(); else problem_count++;
|
||||
}
|
||||
|
||||
@h Error messages.
|
||||
@ \section{Error messages.}
|
||||
Ah, they kill you; or they don't. The fatal kind cause an exit code of 2, to
|
||||
distinguish this from a proper completion in which non-fatal errors occur.
|
||||
These two routines (alone) can be caused by failures of the memory allocation
|
||||
or streams systems, and therefore must be written with a little care to use
|
||||
the temporary stream, not some other string which might need fresh allocation.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Errors::fatal(char *message) {
|
||||
TEMPORARY_TEXT(ERM)
|
||||
WRITE_TO(ERM, "%s: fatal error: %s\n", PROGRAM_NAME, message);
|
||||
|
@ -80,13 +80,13 @@ void Errors::fatal_with_path(char *message, pathname *P) {
|
|||
}
|
||||
|
||||
@ Assertion failures lead to the following. Note the use of the C11 syntax
|
||||
|_Noreturn|; this seems now to be well-supported on modern C compilers, though.
|
||||
[[_Noreturn]]; this seems now to be well-supported on modern C compilers, though.
|
||||
(It needs to be on its own line to avoid hassle with InC, which has no special
|
||||
support for C annotations.)
|
||||
|
||||
@d internal_error(message) Errors::internal_error_handler(NULL, message, __FILE__, __LINE__)
|
||||
#define internal_error(message) Errors::internal_error_handler(NULL, message, __FILE__, __LINE__)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
_Noreturn
|
||||
void Errors::internal_error_handler(void *p, char *message, char *f, int lc) {
|
||||
if (internal_errors_handler)
|
||||
|
@ -96,12 +96,12 @@ void Errors::internal_error_handler(void *p, char *message, char *f, int lc) {
|
|||
exit(1); /* redundant but needed to remove compiler warning in clang */
|
||||
}
|
||||
|
||||
@h Deliberately crashing.
|
||||
@ \section{Deliberately crashing.}
|
||||
It's sometimes convenient to get a backtrace from the debugger when an error
|
||||
occurs unexpectedly, and one way to do that is to force a division by zero.
|
||||
(This is only enabled by |-crash| at the command line and is for debugging only.)
|
||||
(This is only enabled by [[-crash]] at the command line and is for debugging only.)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int debugger_mode = FALSE;
|
||||
void Errors::enter_debugger_mode(void) {
|
||||
debugger_mode = TRUE;
|
||||
|
@ -119,11 +119,11 @@ void Errors::die(void) { /* as void as it gets */
|
|||
exit(2);
|
||||
}
|
||||
|
||||
@h Survivable errors.
|
||||
@ \section{Survivable errors.}
|
||||
The trick with error messages is to indicate where they occur, and we can
|
||||
specify this at three levels of abstraction:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Errors::nowhere(char *message) {
|
||||
Errors::in_text_file(message, NULL);
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ void Errors::in_text_file_S(text_stream *message, text_file_position *here) {
|
|||
|
||||
@ Which funnel through:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Errors::at_position(char *message, filename *file, int line) {
|
||||
TEMPORARY_TEXT(ERM)
|
||||
WRITE_TO(ERM, "%s: ", PROGRAM_NAME);
|
||||
|
@ -165,7 +165,7 @@ void Errors::at_position_S(text_stream *message, filename *file, int line) {
|
|||
|
||||
@ Lastly:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Errors::with_file(char *message, filename *F) {
|
||||
TEMPORARY_TEXT(ERM)
|
||||
WRITE_TO(ERM, "%s: %f: %s\n", PROGRAM_NAME, F, message);
|
|
@ -2,23 +2,23 @@
|
|||
|
||||
Names of hypothetical or real files in the filing system.
|
||||
|
||||
@h Storage.
|
||||
@ \section{Storage.}
|
||||
Filename objects behave much like pathname ones, but they have their own
|
||||
structure in order that the two are distinct C types. Individual filenames
|
||||
are a single instance of the following. (Note that the text part is stored
|
||||
as Unicode code points, regardless of what text encoding the locale has.)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct filename {
|
||||
struct pathname *pathname_of_location;
|
||||
struct text_stream *leafname;
|
||||
CLASS_DEFINITION
|
||||
} filename;
|
||||
|
||||
@h Creation.
|
||||
@ \section{Creation.}
|
||||
A filename is made by supplying a pathname and a leafname.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
filename *Filenames::in(pathname *P, text_stream *file_name) {
|
||||
return Filenames::primitive(file_name, 0, Str::len(file_name), P);
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ filename *Filenames::primitive(text_stream *S, int from, int to, pathname *P) {
|
|||
return F;
|
||||
}
|
||||
|
||||
@h Strings to filenames.
|
||||
@ \section{Strings to filenames.}
|
||||
The following takes a textual name and returns a filename.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
filename *Filenames::from_text(text_stream *path) {
|
||||
int i = 0, pos = -1;
|
||||
LOOP_THROUGH_TEXT(at, path) {
|
||||
|
@ -68,10 +68,10 @@ filename *Filenames::from_text_relative(pathname *from, text_stream *path) {
|
|||
return F;
|
||||
}
|
||||
|
||||
@h The writer.
|
||||
@ \section{The writer.}
|
||||
And conversely:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Filenames::writer(OUTPUT_STREAM, char *format_string, void *vF) {
|
||||
filename *F = (filename *) vF;
|
||||
if (F == NULL) WRITE("<no file>");
|
||||
|
@ -87,7 +87,7 @@ void Filenames::writer(OUTPUT_STREAM, char *format_string, void *vF) {
|
|||
|
||||
@ And again relative to a given pathname:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Filenames::to_text_relative(OUTPUT_STREAM, filename *F, pathname *P) {
|
||||
TEMPORARY_TEXT(ft)
|
||||
TEMPORARY_TEXT(pt)
|
||||
|
@ -109,17 +109,17 @@ void Filenames::to_text_relative(OUTPUT_STREAM, filename *F, pathname *P) {
|
|||
DISCARD_TEXT(pt)
|
||||
}
|
||||
|
||||
@h Reading off the directory.
|
||||
@ \section{Reading off the directory.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
pathname *Filenames::up(filename *F) {
|
||||
if (F == NULL) return NULL;
|
||||
return F->pathname_of_location;
|
||||
}
|
||||
|
||||
@h Reading off the leafname.
|
||||
@ \section{Reading off the leafname.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
filename *Filenames::without_path(filename *F) {
|
||||
return Filenames::in(NULL, F->leafname);
|
||||
}
|
||||
|
@ -137,14 +137,14 @@ void Filenames::write_unextended_leafname(OUTPUT_STREAM, filename *F) {
|
|||
}
|
||||
}
|
||||
|
||||
@h Filename extensions.
|
||||
@ \section{Filename extensions.}
|
||||
The following is cautiously written because of an oddity in Windows's handling
|
||||
of filenames, which are allowed to have trailing dots or spaces, in a way
|
||||
which isn't necessarily visible to the user, who may have added these by
|
||||
an accidental brush of the keyboard. Thus |frog.jpg .| should be treated
|
||||
as equivalent to |frog.jpg| when deciding the likely file format.
|
||||
an accidental brush of the keyboard. Thus [[frog.jpg .]] should be treated
|
||||
as equivalent to [[frog.jpg]] when deciding the likely file format.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Filenames::write_extension(OUTPUT_STREAM, filename *F) {
|
||||
int on = FALSE;
|
||||
LOOP_THROUGH_TEXT(pos, F->leafname) {
|
||||
|
@ -170,23 +170,24 @@ filename *Filenames::set_extension(filename *F, text_stream *extension) {
|
|||
return N;
|
||||
}
|
||||
|
||||
@h Guessing file formats.
|
||||
@ \section{Guessing file formats.}
|
||||
The following guesses the file format from its file extension:
|
||||
|
||||
@d FORMAT_PERHAPS_HTML 1
|
||||
@d FORMAT_PERHAPS_JPEG 2
|
||||
@d FORMAT_PERHAPS_PNG 3
|
||||
@d FORMAT_PERHAPS_OGG 4
|
||||
@d FORMAT_PERHAPS_AIFF 5
|
||||
@d FORMAT_PERHAPS_MIDI 6
|
||||
@d FORMAT_PERHAPS_MOD 7
|
||||
@d FORMAT_PERHAPS_GLULX 8
|
||||
@d FORMAT_PERHAPS_ZCODE 9
|
||||
@d FORMAT_PERHAPS_SVG 10
|
||||
@d FORMAT_PERHAPS_GIF 11
|
||||
@d FORMAT_UNRECOGNISED 0
|
||||
<<*>>=
|
||||
#define FORMAT_PERHAPS_HTML 1
|
||||
#define FORMAT_PERHAPS_JPEG 2
|
||||
#define FORMAT_PERHAPS_PNG 3
|
||||
#define FORMAT_PERHAPS_OGG 4
|
||||
#define FORMAT_PERHAPS_AIFF 5
|
||||
#define FORMAT_PERHAPS_MIDI 6
|
||||
#define FORMAT_PERHAPS_MOD 7
|
||||
#define FORMAT_PERHAPS_GLULX 8
|
||||
#define FORMAT_PERHAPS_ZCODE 9
|
||||
#define FORMAT_PERHAPS_SVG 10
|
||||
#define FORMAT_PERHAPS_GIF 11
|
||||
#define FORMAT_UNRECOGNISED 0
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Filenames::guess_format(filename *F) {
|
||||
TEMPORARY_TEXT(EXT)
|
||||
Filenames::write_extension(EXT, F);
|
||||
|
@ -224,13 +225,13 @@ int Filenames::guess_format(filename *F) {
|
|||
return verdict;
|
||||
}
|
||||
|
||||
@h Opening.
|
||||
These files are wrappers for |fopen|, the traditional C library call, but
|
||||
@ \section{Opening.}
|
||||
These files are wrappers for [[fopen]], the traditional C library call, but
|
||||
referring to the file by filename structure rather than a textual name. Note
|
||||
that we must transcode the filename to whatever the locale expects before
|
||||
we call |fopen|, which is the main reason for the wrapper.
|
||||
we call [[fopen]], which is the main reason for the wrapper.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
FILE *Filenames::fopen(filename *F, char *usage) {
|
||||
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
|
||||
TEMPORARY_TEXT(FN)
|
||||
|
@ -249,12 +250,12 @@ FILE *Filenames::fopen_caseless(filename *F, char *usage) {
|
|||
return CIFilingSystem::fopen(transcoded_pathname, usage);
|
||||
}
|
||||
|
||||
@h Comparing.
|
||||
@ \section{Comparing.}
|
||||
Not as easy as it seems. The following is a slow but effective way to
|
||||
compare two filenames by seeing if they have the same canonical form
|
||||
when printed out.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Filenames::eq(filename *F1, filename *F2) {
|
||||
if (F1 == F2) return TRUE;
|
||||
TEMPORARY_TEXT(T1)
|
||||
|
@ -267,9 +268,9 @@ int Filenames::eq(filename *F1, filename *F2) {
|
|||
return rv;
|
||||
}
|
||||
|
||||
@h Timestamps and sizes.
|
||||
@ \section{Timestamps and sizes.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
time_t Filenames::timestamp(filename *F) {
|
||||
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
|
||||
TEMPORARY_TEXT(FN)
|
|
@ -2,39 +2,39 @@
|
|||
|
||||
Locations of hypothetical or real directories in the filing system.
|
||||
|
||||
@h About pathnames.
|
||||
@ \section{About pathnames.}
|
||||
We use the word "pathname" to mean a file-system location of a directory,
|
||||
and "filename" to mean a location of a file. For example:
|
||||
= (text)
|
||||
|
||||
/Users/rblackmore/Documents/Fireball
|
||||
=
|
||||
|
||||
is a pathname, whereas
|
||||
= (text)
|
||||
|
||||
/Users/rblackmore/Documents/Fireball/whoosh.aiff
|
||||
=
|
||||
|
||||
is a filename. All references to directory locations in the filing system will be
|
||||
held internally as |pathname| objects, and all references to file locations as
|
||||
|filename| objects. Once created, these are never destroyed or modified,
|
||||
held internally as [[pathname]] objects, and all references to file locations as
|
||||
[[filename]] objects. Once created, these are never destroyed or modified,
|
||||
so that it's safe to store a pointer to a pathname or filename anywhere.
|
||||
|
||||
Note that a pathname may well be hypothetical, that is, it may well
|
||||
describe a directory which doesn't exist on disc.
|
||||
|
||||
A full path is a linked list, but reverse-ordered: thus,
|
||||
= (text)
|
||||
|
||||
/Users/rblackmore/Documents/
|
||||
=
|
||||
would be represented as a pointer to the |pathname| for "Documents", which
|
||||
|
||||
would be represented as a pointer to the [[pathname]] for "Documents", which
|
||||
in turn points to one for "rblackmore", which in turn points to "/Users".
|
||||
Thus the root of the filing system is represented by the null pointer.
|
||||
|
||||
Each |pathname| can represent only a single level in the hierarchy, and
|
||||
its textual name is not allowed to contain the |FOLDER_SEPARATOR| character,
|
||||
with just one exception: the |pathname| at the end of the chain is allowed
|
||||
to begin with |FOLDER_SEPARATOR| to denote that it's at the root of the
|
||||
Each [[pathname]] can represent only a single level in the hierarchy, and
|
||||
its textual name is not allowed to contain the [[FOLDER_SEPARATOR]] character,
|
||||
with just one exception: the [[pathname]] at the end of the chain is allowed
|
||||
to begin with [[FOLDER_SEPARATOR]] to denote that it's at the root of the
|
||||
host file system.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct pathname {
|
||||
struct text_stream *intermediate;
|
||||
struct pathname *pathname_of_parent;
|
||||
|
@ -42,11 +42,11 @@ typedef struct pathname {
|
|||
CLASS_DEFINITION
|
||||
} pathname;
|
||||
|
||||
@h Home directory.
|
||||
@ \section{Home directory.}
|
||||
We get the path to the user's home directory from the environment variable
|
||||
|HOME|, if it exists.
|
||||
[[HOME]], if it exists.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
pathname *home_path = NULL;
|
||||
void Pathnames::start(void) {
|
||||
char *home = (char *) (Platform::getenv("HOME"));
|
||||
|
@ -57,9 +57,9 @@ void Pathnames::start(void) {
|
|||
}
|
||||
}
|
||||
|
||||
@h Installation directory.
|
||||
@ \section{Installation directory.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
pathname *installation_path = NULL;
|
||||
void Pathnames::set_installation_path(pathname *P) {
|
||||
installation_path = P;
|
||||
|
@ -88,11 +88,11 @@ pathname *Pathnames::installation_path(const char *V, text_stream *def) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
@h Creation.
|
||||
A subdirectory is made by taking an existing pathname (or possible |NULL|) and
|
||||
@ \section{Creation.}
|
||||
A subdirectory is made by taking an existing pathname (or possible [[NULL]]) and
|
||||
then going one level deeper, using the supplied name.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
pathname *Pathnames::down(pathname *P, text_stream *dir_name) {
|
||||
return Pathnames::primitive(dir_name, 0, Str::len(dir_name), P);
|
||||
}
|
||||
|
@ -109,14 +109,14 @@ pathname *Pathnames::primitive(text_stream *str, int from, int to, pathname *par
|
|||
return P;
|
||||
}
|
||||
|
||||
@h Text to pathnames.
|
||||
@ \section{Text to pathnames.}
|
||||
The following takes a text of a name and returns a pathname,
|
||||
possibly relative to the home directory. Empty directory names are ignored
|
||||
except possibly for an initial slash, so for example |paris/roubaix|,
|
||||
|paris//roubaix| and |paris/roubaix/| are indistinguishable here, but
|
||||
|/paris/roubaix| is different.
|
||||
except possibly for an initial slash, so for example [[paris/roubaix]],
|
||||
[[paris//roubaix]] and [[paris/roubaix/]] are indistinguishable here, but
|
||||
[[/paris/roubaix]] is different.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
pathname *Pathnames::from_text(text_stream *path) {
|
||||
return Pathnames::from_text_relative(NULL, path);
|
||||
}
|
||||
|
@ -134,10 +134,10 @@ pathname *Pathnames::from_text_relative(pathname *P, text_stream *path) {
|
|||
return at;
|
||||
}
|
||||
|
||||
@h Writer.
|
||||
@ \section{Writer.}
|
||||
Conversely, by the miracle of depth-first recursion:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Pathnames::writer(OUTPUT_STREAM, char *format_string, void *vP) {
|
||||
pathname *P = (pathname *) vP;
|
||||
int divider = FOLDER_SEPARATOR;
|
||||
|
@ -153,28 +153,28 @@ void Pathnames::writer_r(OUTPUT_STREAM, pathname *P, int divider) {
|
|||
WRITE("%S", P->intermediate);
|
||||
}
|
||||
|
||||
@h Relative pathnames.
|
||||
@ \section{Relative pathnames.}
|
||||
Occasionally we want to shorten a pathname relative to another one:
|
||||
for example,
|
||||
= (text)
|
||||
|
||||
/Users/rblackmore/Documents/Fireball/tablature
|
||||
=
|
||||
|
||||
relative to
|
||||
= (text)
|
||||
|
||||
/Users/rblackmore/Documents/
|
||||
=
|
||||
|
||||
would be
|
||||
= (text)
|
||||
|
||||
Fireball/tablature
|
||||
=
|
||||
|
||||
If the two pathnames are the same, the relative pathname is the empty text,
|
||||
and so nothing is output.
|
||||
|
||||
Note that this does not correctly handle symlinks, |.|, |..| and so on,
|
||||
Note that this does not correctly handle symlinks, [[.]], [[..]] and so on,
|
||||
so it's probably not wise to use it with filenames typed in at the command
|
||||
line.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Pathnames::to_text_relative(OUTPUT_STREAM, pathname *P, pathname *R) {
|
||||
TEMPORARY_TEXT(rt)
|
||||
TEMPORARY_TEXT(pt)
|
||||
|
@ -200,12 +200,12 @@ text_stream *Pathnames::directory_name(pathname *P) {
|
|||
return P->intermediate;
|
||||
}
|
||||
|
||||
@h Relative URLs.
|
||||
Suppose a web page in the directory at |from| wants to link to a page in
|
||||
the directory |to|. The following composes a minimal-length URL to do so:
|
||||
@ \section{Relative URLs.}
|
||||
Suppose a web page in the directory at [[from]] wants to link to a page in
|
||||
the directory [[to]]. The following composes a minimal-length URL to do so:
|
||||
possibly, if they are in fact the same directory, an empty one.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Pathnames::relative_URL(OUTPUT_STREAM, pathname *from, pathname *to) {
|
||||
TEMPORARY_TEXT(url)
|
||||
int found = FALSE;
|
||||
|
@ -237,11 +237,11 @@ void Pathnames::relative_URL(OUTPUT_STREAM, pathname *from, pathname *to) {
|
|||
DISCARD_TEXT(url)
|
||||
}
|
||||
|
||||
@h Existence in the file system.
|
||||
@ \section{Existence in the file system.}
|
||||
Just because we have a pathname, it doesn't follow that any directory exists
|
||||
on the file system with that path.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Pathnames::create_in_file_system(pathname *P) {
|
||||
if (P == NULL) return TRUE; /* the root of the file system always exists */
|
||||
if (P->known_to_exist) return TRUE;
|
||||
|
@ -254,12 +254,12 @@ int Pathnames::create_in_file_system(pathname *P) {
|
|||
return P->known_to_exist;
|
||||
}
|
||||
|
||||
@h Directory synchronisation.
|
||||
@ \section{Directory synchronisation.}
|
||||
Both pathnames here represent directories which do exist. The function makes
|
||||
the |dest| tree an exact copy of the |source| tree (and therefore deletes
|
||||
anything different which was originally in |dest|).
|
||||
the [[dest]] tree an exact copy of the [[source]] tree (and therefore deletes
|
||||
anything different which was originally in [[dest]]).
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Pathnames::rsync(pathname *source, pathname *dest) {
|
||||
char transcoded_source[4*MAX_FILENAME_LENGTH];
|
||||
TEMPORARY_TEXT(pn)
|
|
@ -3,14 +3,14 @@
|
|||
Sending commands to the shell, on Unix-like platforms, or simulating
|
||||
this on Windows.
|
||||
|
||||
@h Operating system interface.
|
||||
@ \section{Operating system interface.}
|
||||
Some of our programs have to issue commands to the host operating system,
|
||||
to copy files, pass them through TeX, and so on. All of that is done using
|
||||
the C standard library |system| function; the commands invoked are all
|
||||
the C standard library [[system]] function; the commands invoked are all
|
||||
standard for POSIX, so will work on MacOS and Linux, but on a Windows system
|
||||
they would need to be read in a POSIX-style environment like Cygwin.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Shell::quote_path(OUTPUT_STREAM, pathname *P) {
|
||||
TEMPORARY_TEXT(FN)
|
||||
WRITE_TO(FN, "%p", P);
|
||||
|
@ -44,9 +44,9 @@ void Shell::quote_text(OUTPUT_STREAM, text_stream *raw) {
|
|||
PUT(' ');
|
||||
}
|
||||
|
||||
@ The generic shell code to apply |command| to a file |F|:
|
||||
@ The generic shell code to apply [[command]] to a file [[F]]:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Shell::apply(char *command, filename *F) {
|
||||
TEMPORARY_TEXT(COMMAND)
|
||||
Shell::plain(COMMAND, command);
|
||||
|
@ -64,9 +64,9 @@ void Shell::apply_S(text_stream *command, filename *F) {
|
|||
DISCARD_TEXT(COMMAND)
|
||||
}
|
||||
|
||||
@ Applications to using |rm| and |cp|:
|
||||
@ Applications to using [[rm]] and [[cp]]:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Shell::rm(filename *F) {
|
||||
Shell::apply("rm", F);
|
||||
}
|
||||
|
@ -83,25 +83,26 @@ void Shell::copy(filename *F, pathname *T, char *options) {
|
|||
}
|
||||
|
||||
@ This writes the traditional Unix shell syntax for redirecting the output
|
||||
from both |stdout| and |stderr| to the same named file.
|
||||
from both [[stdout]] and [[stderr]] to the same named file.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Shell::redirect(OUTPUT_STREAM, filename *F) {
|
||||
Shell::plain(OUT, ">");
|
||||
Shell::quote_file(OUT, F);
|
||||
Shell::plain(OUT, "2>&1");
|
||||
}
|
||||
|
||||
@h Actual commands.
|
||||
@ \section{Actual commands.}
|
||||
The scheme is that commands are composed using the above functions, and
|
||||
then sent to this one.
|
||||
|
||||
We make the buffer here long enough for 8 filenames of worst-case length,
|
||||
all transcoded to UTF-8 in the most unlucky way imaginable.
|
||||
|
||||
@d SPOOL_LENGTH 4*8*MAX_FILENAME_LENGTH
|
||||
<<*>>=
|
||||
#define SPOOL_LENGTH 4*8*MAX_FILENAME_LENGTH
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int shell_verbosity = FALSE;
|
||||
void Shell::verbose(void) {
|
||||
shell_verbosity = TRUE;
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
Managing how we record and use the current time and date.
|
||||
|
||||
@h Clock.
|
||||
@ \section{Clock.}
|
||||
From the local environment, we'll extract the time at which we're running.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
time_t right_now;
|
||||
struct tm *the_present = NULL;
|
||||
int fix_time_mode = FALSE;
|
||||
|
@ -16,13 +16,13 @@ void Time::begin(void) {
|
|||
fix_time_mode = FALSE;
|
||||
}
|
||||
|
||||
@ The command line option |-fixtime| causes any tool compiled with Foundation
|
||||
@ The command line option [[-fixtime]] causes any tool compiled with Foundation
|
||||
to fix the date as 11 a.m. on 28 March 2016, which is Inform's birthday. This
|
||||
makes it easier to automate testing, since we can compare output generated
|
||||
in one session with output generated another, even though that was on two
|
||||
different dates.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Time::fix(void) {
|
||||
struct tm start;
|
||||
start.tm_sec = 0; start.tm_min = 0; start.tm_hour = 11;
|
||||
|
@ -36,7 +36,7 @@ int Time::fixed(void) {
|
|||
return fix_time_mode;
|
||||
}
|
||||
|
||||
@h Calendrical.
|
||||
@ \section{Calendrical.}
|
||||
The date of Easter depends on who and where you are. Inform's notional home
|
||||
is England. Following League of Nations advice in 1926, Easter is legally
|
||||
celebrated in England on the Sunday after the second Saturday in April, unless
|
||||
|
@ -75,7 +75,7 @@ practically a random-number generator. The one thing to be said in its
|
|||
favour is that it can be computed accurately with integer arithmetic using
|
||||
fairly low numbers, and this we now do.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Time::easter(int year, int *d, int *m) {
|
||||
int c, y, k, i, n, j, l;
|
||||
y = year;
|
||||
|
@ -94,11 +94,12 @@ void Time::easter(int year, int *d, int *m) {
|
|||
|
||||
@ And we can use this to tell if the season's merry:
|
||||
|
||||
@d CHRISTMAS_FEAST 1
|
||||
@d EASTER_FEAST 2
|
||||
@d NON_FEAST 3
|
||||
<<*>>=
|
||||
#define CHRISTMAS_FEAST 1
|
||||
#define EASTER_FEAST 2
|
||||
#define NON_FEAST 3
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Time::feast(void) {
|
||||
int this_month = the_present->tm_mon + 1;
|
||||
int this_day = the_present->tm_mday;
|
||||
|
@ -118,30 +119,30 @@ int Time::feast(void) {
|
|||
return NON_FEAST;
|
||||
}
|
||||
|
||||
@h Stopwatch timings.
|
||||
@ \section{Stopwatch timings.}
|
||||
The following provides a sort of hierarchical stopwatch. In principle it
|
||||
could time anything (though not very accurately), but it's mainly intended
|
||||
for monitoring how long programs internally work, since it reads time from
|
||||
the |clock()| (i.e., how much CPU time the current process has taken) rather
|
||||
the [[clock()]] (i.e., how much CPU time the current process has taken) rather
|
||||
than from the actual time of day.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct stopwatch_timer {
|
||||
int running; /* set if this has been started but not stopped */
|
||||
struct text_stream *event;
|
||||
clock_t start_time;
|
||||
clock_t end_time;
|
||||
int time_taken; /* measured in centiseconds of CPU time */
|
||||
linked_list *stages_chronological; /* of |stopwatch_timer| */
|
||||
linked_list *stages_sorted; /* of |stopwatch_timer| */
|
||||
linked_list *stages_chronological; /* of [[stopwatch_timer]] */
|
||||
linked_list *stages_sorted; /* of [[stopwatch_timer]] */
|
||||
CLASS_DEFINITION
|
||||
} stopwatch_timer;
|
||||
|
||||
@ If |within| is not null, it must be another stopwatch which is also running;
|
||||
@ If [[within]] is not null, it must be another stopwatch which is also running;
|
||||
the idea is that the new stopwatch is to time a sub-task of the main task which
|
||||
|within| is timing.
|
||||
[[within]] is timing.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
stopwatch_timer *Time::start_stopwatch(stopwatch_timer *within, text_stream *name) {
|
||||
stopwatch_timer *st = CREATE(stopwatch_timer);
|
||||
st->event = Str::duplicate(name);
|
||||
|
@ -162,7 +163,7 @@ stopwatch_timer *Time::start_stopwatch(stopwatch_timer *within, text_stream *nam
|
|||
@ Every started stopwatch must be stopped in order to register time having
|
||||
been used. Once this is done, we sort:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Time::stop_stopwatch(stopwatch_timer *st) {
|
||||
if (st->running == FALSE) internal_error("already stopped");
|
||||
st->running = FALSE;
|
||||
|
@ -170,11 +171,11 @@ int Time::stop_stopwatch(stopwatch_timer *st) {
|
|||
st->time_taken +=
|
||||
(((int) (st->end_time)) - ((int) (st->start_time))) / ((int) (CLOCKS_PER_SEC/100));
|
||||
int N = LinkedLists::len(st->stages_chronological);
|
||||
if (N > 0) @<Sort the subtasks in descreasing order of how much time they took@>;
|
||||
if (N > 0) <<Sort the subtasks in descreasing order of how much time they took>>;
|
||||
return st->time_taken;
|
||||
}
|
||||
|
||||
@<Sort the subtasks in descreasing order of how much time they took@> =
|
||||
<<Sort the subtasks in descreasing order of how much time they took>>=
|
||||
st->stages_sorted = NEW_LINKED_LIST(stopwatch_timer);
|
||||
stopwatch_timer **as_array = (stopwatch_timer **)
|
||||
(Memory::calloc(N, sizeof(stopwatch_timer *), ARRAY_SORTING_MREASON));
|
||||
|
@ -189,11 +190,11 @@ int Time::stop_stopwatch(stopwatch_timer *st) {
|
|||
|
||||
@ This sorts first by elapsed time, then by event name in alphabetical order:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Time::compare_watches(const void *w1, const void *w2) {
|
||||
const stopwatch_timer **st1 = (const stopwatch_timer **) w1;
|
||||
const stopwatch_timer **st2 = (const stopwatch_timer **) w2;
|
||||
if ((*st1 == NULL) || (*st2 == NULL))
|
||||
if ((*st1 == NULL) [[]] (*st2 == NULL))
|
||||
internal_error("Disaster while sorting stopwatch timings");
|
||||
int t1 = (*st1)->time_taken, t2 = (*st2)->time_taken;
|
||||
if (t1 > t2) return -1;
|
||||
|
@ -204,7 +205,7 @@ int Time::compare_watches(const void *w1, const void *w2) {
|
|||
@ Once started and then stopped, a stopwatch can be "resumed", provided it
|
||||
is then stopped again. The elapsed time is accumulated.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Time::resume_stopwatch(stopwatch_timer *st) {
|
||||
if (st->running) internal_error("already running");
|
||||
st->running = TRUE;
|
||||
|
@ -213,10 +214,10 @@ void Time::resume_stopwatch(stopwatch_timer *st) {
|
|||
}
|
||||
|
||||
@ All of which enables a neat hierarchical printout. The task is timed to
|
||||
an accuracy of 1/1000th of the |total| supplied, and sub-tasks taking less
|
||||
an accuracy of 1/1000th of the [[total]] supplied, and sub-tasks taking less
|
||||
than that are omitted from the log.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Time::log_timing(stopwatch_timer *st, int total) {
|
||||
if (st) {
|
||||
int N = 1000*st->time_taken/total;
|
Loading…
Reference in a new issue