From c9e2bf6cb988f8c00d89ccf191a28388cccbd868 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 21 Aug 2005 07:21:06 +0000 Subject: [PATCH] Revamp SAVE, XSAVE, EXPORT. Add (or at least parse) all the subcommands that we didn't support. Fix bug 13911. Fix bug reported by Adam Pierson (COMPRESSED and other subcommands didn't work on SAVE). Refactor all related code. --- doc/files.texi | 117 +++++- src/ChangeLog | 71 ++++ src/aggregate.c | 3 +- src/case.h | 4 +- src/command.def | 1 + src/command.h | 4 +- src/dictionary.c | 17 + src/dictionary.h | 1 + src/expressions/helpers.c | 2 +- src/expressions/helpers.h | 2 +- src/get.c | 655 ++++++++++++++++++--------------- src/lexer.c | 16 + src/lexer.h | 2 + src/pfm-read.h | 7 - src/pfm-write.c | 80 +++- src/pfm-write.h | 22 +- src/sfm-write.c | 65 +++- src/sfm-write.h | 15 +- tests/ChangeLog | 6 + tests/command/import-export.sh | 20 +- tests/command/sysfiles-old.sh | 2 +- 21 files changed, 724 insertions(+), 388 deletions(-) diff --git a/doc/files.texi b/doc/files.texi index 6f723213..ea1ff885 100644 --- a/doc/files.texi +++ b/doc/files.texi @@ -12,10 +12,11 @@ portable files. * MATCH FILES:: Merge system files. * SAVE:: Write to a system file. * SYSFILE INFO:: Display system file dictionary. -* XSAVE:: Write to a system file, as a transform. +* XEXPORT:: Write to a portable file, as a transformation. +* XSAVE:: Write to a system file, as a transformation. @end menu -@node APPLY DICTIONARY, EXPORT, System and Portable Files, System and Portable Files +@node APPLY DICTIONARY @section APPLY DICTIONARY @vindex APPLY DICTIONARY @@ -61,21 +62,38 @@ the active file weighting variable. active file. The system file is not modified. -@node EXPORT, GET, APPLY DICTIONARY, System and Portable Files +@node EXPORT @section EXPORT @vindex EXPORT @display EXPORT /OUTFILE='filename' + /UNSELECTED=@{RETAIN,DELETE@} + /DIGITS=n /DROP=var_list /KEEP=var_list /RENAME=(src_names=target_names)@dots{} + /TYPE=@{COMM,TAPE@} + /MAP @end display The @cmd{EXPORT} procedure writes the active file dictionary and data to a specified portable file. +By default, cases excluded with FILTER are written to the portable +file. These can be excluded by specifying DELETE on the UNSELECTED +subcommand. Specifying RETAIN makes the default explicit. + +Portable files express real numbers in base 30. Integers are always +expressed to the maximum precision needed to make them exact. +Non-integers are, by default, expressed to the machine's maximum +natural precision (approximately 15 decimal digits on many machines). +If many numbers require this many digits, the portable file may +significantly increase in size. As an alternative, the DIGITS +subcommand may be used to specify the number of decimal digits of +precision to write. DIGITS applies only to non-integers. + The OUTFILE subcommand, which is the only required subcommand, specifies the portable file to be written as a file name string or a file handle (@pxref{FILE HANDLE}). @@ -83,9 +101,14 @@ the portable file to be written as a file name string or a file handle DROP, KEEP, and RENAME follow the same format as the SAVE procedure (@pxref{SAVE}). +The TYPE subcommand specifies the character set for use in the +portable file. Its value is currently not used. + +The MAP subcommand is currently ignored. + @cmd{EXPORT} is a procedure. It causes the active file to be read. -@node GET, IMPORT, EXPORT, System and Portable Files +@node GET @section GET @vindex GET @@ -130,7 +153,7 @@ is affected by these subcommands. @cmd{GET} does not cause the data to be read, only the dictionary. The data is read later, when a procedure is executed. -@node IMPORT, MATCH FILES, GET, System and Portable Files +@node IMPORT @section IMPORT @vindex IMPORT @@ -158,7 +181,7 @@ DROP, KEEP, and RENAME follow the syntax used by @cmd{GET} (@pxref{GET}). @cmd{IMPORT} does not cause the data to be read, only the dictionary. The data is read later, when a procedure is executed. -@node MATCH FILES, SAVE, IMPORT, System and Portable Files +@node MATCH FILES @section MATCH FILES @vindex MATCH FILES @@ -224,18 +247,22 @@ FIRST, LAST, and MAP are currently ignored. @cmd{MATCH FILES} may not be specified following @cmd{TEMPORARY} (@pxref{TEMPORARY}) if the active file is used as an input source. -@node SAVE, SYSFILE INFO, MATCH FILES, System and Portable Files +@node SAVE @section SAVE @vindex SAVE @display SAVE /OUTFILE='filename' + /UNSELECTED=@{RETAIN,DELETE@} /@{COMPRESSED,UNCOMPRESSED@} + /PERMISSIONS=@{WRITEABLE,READONLY@} /DROP=var_list /KEEP=var_list /VERSION=version /RENAME=(src_names=target_names)@dots{} + /NAMES + /MAP @end display The @cmd{SAVE} procedure causes the dictionary and data in the active @@ -246,10 +273,18 @@ OUTFILE is the only required subcommand. Specify the system file to be written as a string file name or a file handle (@pxref{FILE HANDLE}). +By default, cases excluded with FILTER are written to the system file. +These can be excluded by specifying DELETE on the UNSELECTED +subcommand. Specifying RETAIN makes the default explicit. + The COMPRESS and UNCOMPRESS subcommand determine whether the saved system file is compressed. By default, system files are compressed. This default can be changed with the SET command (@pxref{SET}). +The PERMISSIONS subcommand specifies permissions for the new system +file. WRITEABLE, the default, creates the file with read and write +permission. READONLY creates the file for read-only access. + By default, all the variables in the active file dictionary are written to the system file. The DROP subcommand can be used to specify a list of variables not to be written. In contrast, KEEP specifies variables @@ -274,14 +309,15 @@ the active file. DROP, KEEP, and RENAME only affect the system file written to disk. The VERSION subcommand specifies the version of the file format. Valid -versions are '3' and '3x'. Version 3x system files are identical to -version 3 files, except that variable names greater than 8 bytes will -be truncated. The default version is 3. The VERSION subcommand is -optional. There is no need ever to use it. +versions are 2 and 3. The default version is 3. In version 2 system +files, variable names longer than 8 bytes will be truncated. The two +versions are otherwise identical. + +The NAMES and MAP subcommands are currently ignored. @cmd{SAVE} causes the data to be read. It is a procedure. -@node SYSFILE INFO, XSAVE, SAVE, System and Portable Files +@node SYSFILE INFO @section SYSFILE INFO @vindex SYSFILE INFO @@ -297,7 +333,40 @@ a system file and displays information on its dictionary. @cmd{SYSFILE INFO} does not affect the current active file. -@node XSAVE, , SYSFILE INFO, System and Portable Files +@node XEXPORT +@section XEXPORT +@vindex XEXPORT + +@display +EXPORT + /OUTFILE='filename' + /DIGITS=n + /DROP=var_list + /KEEP=var_list + /RENAME=(src_names=target_names)@dots{} + /TYPE=@{COMM,TAPE@} + /MAP +@end display + +The @cmd{EXPORT} transformation writes the active file dictionary and +data to a specified portable file. + +This transformation is a PSPP extension. + +It is similar to the @cmd{EXPORT} procedure, with two differences: + +@itemize +@item +@cmd{XEXPORT} is a transformation, not a procedure. It is executed when +the data is read by a procedure or procedure-like command. + +@item +@cmd{XEXPORT} does not support the UNSELECTED subcommand. +@end itemize + +@xref{EXPORT}, for more information. + +@node XSAVE, , XEXPORT, System and Portable Files @section XSAVE @vindex XSAVE @@ -305,17 +374,27 @@ a system file and displays information on its dictionary. XSAVE /OUTFILE='filename' /@{COMPRESSED,UNCOMPRESSED@} + /PERMISSIONS=@{WRITEABLE,READONLY@} /DROP=var_list /KEEP=var_list + /VERSION=version /RENAME=(src_names=target_names)@dots{} + /NAMES + /MAP @end display The @cmd{XSAVE} transformation writes the active file dictionary and -data to a -system file stored on disk. +data to a system file stored on disk. It is similar to the @cmd{SAVE} +procedure, with two differences: + +@itemize +@item +@cmd{XSAVE} is a transformation, not a procedure. It is executed when +the data is read by a procedure or procedure-like command. + +@item +@cmd{XSAVE} does not support the UNSELECTED subcommand. +@end itemize -@cmd{XSAVE} is a transformation, not a procedure. It is executed when the -data is read by a procedure or procedure-like command. In all other -respects, @cmd{XSAVE} is identical to @cmd{SAVE}. @xref{SAVE}, for -more information on syntax and usage. +@xref{SAVE}, for more information. @setfilename ignored diff --git a/src/ChangeLog b/src/ChangeLog index fb536fd1..dc2c605e 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,74 @@ +Sun Aug 21 00:12:24 2005 Ben Pfaff + + * lexer.c: (lex_sbc_only_once) New function. + (lex_sbc_missing) New function. + +Sun Aug 21 00:00:47 2005 Ben Pfaff + + * case.h: (case_str) Make it return `unsigned char'. + +Sat Aug 20 23:56:14 2005 Ben Pfaff + + Revamp SAVE, XSAVE, EXPORT. Add (or at least parse) all the + subcommands that we didn't support. Fix bug 13911. Fix bug + reported by Adam Pierson (COMPRESSED and other subcommands didn't + work on SAVE). Refactor all related code. + + * command.def: Add XEXPORT command. + + * dictionary.c: (dict_delete_scratch_vars) New function. + + * get.c: (cmd_get) Fix parsing. + (struct save_trns) Removed. + (cmd_save_internal) Removed. + (cmd_save) Removed. + (do_write_case) Removed. + (save_write_case_func) Removed. + (save_trns_proc) Removed. + (save_trns_free) Removed. + (trim_dictionary) Removed. + (struct export_proc) Removed. + (cmd_export) Rewrote. + (export_write_case_func) Removed. + (export_proc_free) Removed. + (enum writer_type) New enum. + (enum command_type) New enum. + (struct any_writer) New struct. + (any_writer_destroy) New function. + (parse_write_command) New function. + (any_writer_write_case) New function. + (parse_output_proc) New function. + (output_proc) New function. + (cmd_save) Rewrote. + (cmd_xsave) Rewrote. + (struct output_trns) New struct. + (parse_output_trns) New function. + (output_trns_proc) New function. + (output_trns_free) New function. + (cmd_xsave) Rewrote. + (cmd_xexport) New function. + (parse_dict_trim) New function. + (struct mtf_proc) Change `by_cnt' member type to `int'. + (cmd_import) Rewrote. + + * pfm-write.c: (struct pfm_writer) Add `digits' member. + (pfm_writer_default_options) New function. + (pfm_open_writer) Add `opts' argument and handle options. + (write_float) Write only as many digits as `digits' member says. + (format_trig_double) Limit base-10 precision to LDBL_DIG. + + * pfm-write.h: (enum pfm_type) Moved here from pfm-read.h. + (struct pfm_write_options) New struct. + + * sfm-write.c: (sfm_writer_default_options) New function. + (sfm_open_writer) Remove `compress', `omit_long_names' args. Add + `opts' argument. Implement options. + + * sfm-write.h: (struct sfm_write_options) New struct. + + * expressions/helpers.c: (copy_string) Make `old' arg `unsigned + char *' instead of `char *'. + Sat Aug 6 21:29:15 2005 Ben Pfaff * factor_stats.c: Needed included earlier. diff --git a/src/aggregate.c b/src/aggregate.c index bc554b97..5979204d 100644 --- a/src/aggregate.c +++ b/src/aggregate.c @@ -278,7 +278,8 @@ cmd_aggregate (void) } else { - agr.writer = sfm_open_writer (out_file, agr.dict, get_scompression (), 0); + agr.writer = sfm_open_writer (out_file, agr.dict, + sfm_writer_default_options ()); if (agr.writer == NULL) goto error; diff --git a/src/case.h b/src/case.h index cf99e022..541a3e2e 100644 --- a/src/case.h +++ b/src/case.h @@ -73,7 +73,7 @@ CASE_INLINE void case_from_values (struct ccase *, CASE_INLINE const union value *case_data (const struct ccase *, size_t idx); CASE_INLINE double case_num (const struct ccase *, size_t idx); -CASE_INLINE const char *case_str (const struct ccase *, size_t idx); +CASE_INLINE const unsigned char *case_str (const struct ccase *, size_t idx); CASE_INLINE union value *case_data_rw (struct ccase *, size_t idx); @@ -170,7 +170,7 @@ case_num (const struct ccase *c, size_t idx) return c->case_data->values[idx].f; } -static inline const char * +static inline const unsigned char * case_str (const struct ccase *c, size_t idx) { return c->case_data->values[idx].s; diff --git a/src/command.def b/src/command.def index fea29e93..fa821b41 100644 --- a/src/command.def +++ b/src/command.def @@ -188,4 +188,5 @@ UNIMPL ("VERIFY", ERRO, ERRO, ERRO, ERRO, "Report time series") DEFCMD ("WEIGHT", ERRO, INPU, TRAN, TRAN, cmd_weight) DEFCMD ("WRITE", ERRO, INPU, TRAN, TRAN, cmd_write) DEFCMD ("WRITE FORMATS", ERRO, INPU, TRAN, TRAN, cmd_write_formats) +DEFCMD ("XEXPORT", ERRO, INPU, TRAN, TRAN, cmd_xexport) DEFCMD ("XSAVE", ERRO, INPU, TRAN, TRAN, cmd_xsave) diff --git a/src/command.h b/src/command.h index 43478ef4..a62f8d60 100644 --- a/src/command.h +++ b/src/command.h @@ -43,9 +43,7 @@ enum extern int pgm_state; extern const char *cur_proc; - -char * pspp_completion_function (const char *text, int state); - +char *pspp_completion_function (const char *text, int state); int cmd_parse (void); diff --git a/src/dictionary.c b/src/dictionary.c index 3f1177d3..c63b2e1b 100644 --- a/src/dictionary.c +++ b/src/dictionary.c @@ -491,6 +491,23 @@ dict_delete_vars (struct dictionary *d, dict_delete_var (d, *vars++); } +/* Deletes scratch variables from dictionary D. */ +void +dict_delete_scratch_vars (struct dictionary *d) +{ + int i; + + /* FIXME: this can be done in O(count) time, but this algorithm + is O(count**2). */ + assert (d != NULL); + + for (i = 0; i < d->var_cnt; ) + if (dict_class_from_id (d->var[i]->name) == DC_SCRATCH) + dict_delete_var (d, d->var[i]); + else + i++; +} + /* Moves V to 0-based position IDX in D. Other variables in D, if any, retain their relative positions. Runs in time linear in the distance moved. */ diff --git a/src/dictionary.h b/src/dictionary.h index 06e190a3..e923d52d 100644 --- a/src/dictionary.h +++ b/src/dictionary.h @@ -54,6 +54,7 @@ int dict_contains_var (const struct dictionary *, const struct variable *); void dict_delete_var (struct dictionary *, struct variable *); void dict_delete_vars (struct dictionary *, struct variable *const *, size_t count); +void dict_delete_scratch_vars (struct dictionary *); void dict_reorder_var (struct dictionary *d, struct variable *v, size_t new_index); void dict_reorder_vars (struct dictionary *, diff --git a/src/expressions/helpers.c b/src/expressions/helpers.c index b4534c81..25fe1437 100644 --- a/src/expressions/helpers.c +++ b/src/expressions/helpers.c @@ -158,7 +158,7 @@ alloc_string (struct expression *e, size_t length) } struct fixed_string -copy_string (struct expression *e, const char *old, size_t length) +copy_string (struct expression *e, const unsigned char *old, size_t length) { struct fixed_string s = alloc_string (e, length); memcpy (s.string, old, length); diff --git a/src/expressions/helpers.h b/src/expressions/helpers.h index 4011d843..0d6082a5 100644 --- a/src/expressions/helpers.h +++ b/src/expressions/helpers.h @@ -52,7 +52,7 @@ double expr_yrmoda (double year, double month, double day); struct fixed_string alloc_string (struct expression *, size_t length); struct fixed_string copy_string (struct expression *, - const char *, size_t length); + const unsigned char *, size_t length); static inline bool is_valid (double d) diff --git a/src/get.c b/src/get.c index f484d120..debc85b7 100644 --- a/src/get.c +++ b/src/get.c @@ -60,8 +60,7 @@ enum operation OP_EXPORT /* EXPORT. */ }; -static bool trim_dictionary (struct dictionary *, - enum operation, int *compress); +static bool parse_dict_trim (struct dictionary *); /* GET input program. */ struct get_pgm @@ -101,8 +100,14 @@ cmd_get (void) case_create (&pgm->bounce, dict_get_next_value_idx (dict)); start_case_map (dict); - if (!trim_dictionary (dict, OP_READ, NULL)) - goto error; + while (lex_match ('/')) + if (!parse_dict_trim (dict)) + goto error; + + if (!lex_end_of_command ()) + return false; + + dict_compact_values (dict); pgm->map = finish_case_map (dict); dict_destroy (default_dict); @@ -114,7 +119,7 @@ cmd_get (void) error: get_pgm_free (pgm); - if (dict != NULL) + if (dict != NULL) dict_destroy (dict); return CMD_FAILURE; } @@ -175,186 +180,352 @@ const struct case_source_class get_source_class = get_source_destroy, }; -/* XSAVE transformation and SAVE procedure. */ -struct save_trns +/* Type of output file. */ +enum writer_type { - struct trns_header h; - struct sfm_writer *writer; /* System file writer. */ - struct case_map *map; /* Map from active file to system file dict. */ - struct ccase bounce; /* Bounce buffer. */ + SYSFILE_WRITER, /* System file. */ + PORFILE_WRITER /* Portable file. */ + }; + +/* Type of a command. */ +enum command_type + { + XFORM_CMD, /* Transformation. */ + PROC_CMD /* Procedure. */ }; -static int save_write_case_func (struct ccase *, void *); -static trns_proc_func save_trns_proc; -static trns_free_func save_trns_free; +/* Portable or system file writer plus a case map. */ +struct any_writer + { + enum writer_type writer_type; + void *writer; + struct case_map *map; /* Map to output file dictionary + (null pointer for identity mapping). */ + struct ccase bounce; /* Bounce buffer for mapping (if needed). */ + }; -/* Parses the SAVE or XSAVE command - and returns the parsed transformation. */ -static struct save_trns * -cmd_save_internal (void) +/* Destroys AW. */ +static void +any_writer_destroy (struct any_writer *aw) { - struct file_handle *fh = NULL; - struct dictionary *dict = NULL; - struct save_trns *t = NULL; - int compress = get_scompression (); - const int default_version = 3; - int version = default_version; - short no_name_table = 0; - - t = xmalloc (sizeof *t); - t->h.proc = save_trns_proc; - t->h.free = save_trns_free; - t->writer = NULL; - t->map = NULL; - case_nullify (&t->bounce); - + if (aw != NULL) + { + switch (aw->writer_type) + { + case PORFILE_WRITER: + pfm_close_writer (aw->writer); + break; + case SYSFILE_WRITER: + sfm_close_writer (aw->writer); + break; + } + destroy_case_map (aw->map); + case_destroy (&aw->bounce); + free (aw); + } +} + +/* Parses SAVE or XSAVE or EXPORT or XEXPORT command. + WRITER_TYPE identifies the type of file to write, + and COMMAND_TYPE identifies the type of command. + + On success, returns a writer. + For procedures only, sets *RETAIN_UNSELECTED to true if cases + that would otherwise be excluded by FILTER or USE should be + included. + + On failure, returns a null pointer. */ +static struct any_writer * +parse_write_command (enum writer_type writer_type, + enum command_type command_type, + bool *retain_unselected) +{ + /* Common data. */ + struct file_handle *handle; /* Output file. */ + struct dictionary *dict; /* Dictionary for output file. */ + struct any_writer *aw; /* Writer. */ + + /* Common options. */ + bool print_map; /* Print map? TODO. */ + bool print_short_names; /* Print long-to-short name map. TODO. */ + struct sfm_write_options sysfile_opts; + struct pfm_write_options porfile_opts; + + assert (writer_type == SYSFILE_WRITER || writer_type == PORFILE_WRITER); + assert (command_type == XFORM_CMD || command_type == PROC_CMD); + assert ((retain_unselected != NULL) == (command_type == PROC_CMD)); + + if (command_type == PROC_CMD) + *retain_unselected = true; - /* Read most of the subcommands. */ + handle = NULL; + dict = dict_clone (default_dict); + aw = xmalloc (sizeof *aw); + aw->writer_type = writer_type; + aw->writer = NULL; + aw->map = NULL; + case_nullify (&aw->bounce); + print_map = false; + print_short_names = false; + sysfile_opts = sfm_writer_default_options (); + porfile_opts = pfm_writer_default_options (); + + start_case_map (dict); + dict_delete_scratch_vars (dict); + + lex_match ('/'); for (;;) { - if (lex_match_id ("VERSION")) + if (lex_match_id ("OUTFILE")) { + if (handle != NULL) + { + lex_sbc_only_once ("OUTFILE"); + goto error; + } + lex_match ('='); - if (lex_force_int ()) - { - version = lex_integer (); - lex_get (); - - if (lex_match_id ("X")) - no_name_table = 1; - } + + handle = fh_parse (); + if (handle == NULL) + goto error; } - else if (lex_match_id ("OUTFILE")) + else if (lex_match_id ("NAMES")) + print_short_names = true; + else if (lex_match_id ("PERMISSIONS")) + { + bool cw; + + lex_match ('='); + if (lex_match_id ("READONLY")) + cw = false; + else if (lex_match_id ("WRITEABLE")) + cw = true; + else + { + lex_error (_("expecting %s or %s"), "READONLY", "WRITEABLE"); + goto error; + } + sysfile_opts.create_writeable = porfile_opts.create_writeable = cw; + } + else if (command_type == PROC_CMD && lex_match_id ("UNSELECTED")) + { + lex_match ('='); + if (lex_match_id ("RETAIN")) + *retain_unselected = true; + else if (lex_match_id ("DELETE")) + *retain_unselected = false; + else + { + lex_error (_("expecting %s or %s"), "RETAIN", "DELETE"); + goto error; + } + } + else if (writer_type == SYSFILE_WRITER && lex_match_id ("COMPRESSED")) + sysfile_opts.compress = true; + else if (writer_type == SYSFILE_WRITER && lex_match_id ("UNCOMPRESSED")) + sysfile_opts.compress = false; + else if (writer_type == SYSFILE_WRITER && lex_match_id ("VERSION")) { lex_match ('='); - - fh = fh_parse (); - if (fh == NULL) - goto error; - + if (!lex_force_int ()) + goto error; + sysfile_opts.version = lex_integer (); + lex_get (); } - if ( ! lex_match('/') ) + else if (writer_type == PORFILE_WRITER && lex_match_id ("TYPE")) + { + lex_match ('='); + if (lex_match_id ("COMMUNICATIONS")) + porfile_opts.type = PFM_COMM; + else if (lex_match_id ("TAPE")) + porfile_opts.type = PFM_TAPE; + else + { + lex_error (_("expecting %s or %s"), "COMM", "TAPE"); + goto error; + } + } + else if (writer_type == PORFILE_WRITER && lex_match_id ("DIGITS")) + { + lex_match ('='); + if (!lex_force_int ()) + goto error; + porfile_opts.digits = lex_integer (); + lex_get (); + } + else if (!parse_dict_trim (dict)) + goto error; + + if (!lex_match ('/')) break; - } + if (lex_end_of_command () != CMD_SUCCESS) + goto error; - if (token != '.') + if (handle == NULL) { - lex_error (_("expecting end of command")); + lex_sbc_missing ("OUTFILE"); goto error; } - if ( fh == NULL ) - { - msg ( ME, _("The required %s subcommand was not present"), "OUTFILE"); - goto error; - } + dict_compact_values (dict); + aw->map = finish_case_map (dict); + if (aw->map != NULL) + case_create (&aw->bounce, dict_get_next_value_idx (dict)); - if ( version != default_version ) + switch (writer_type) { - msg (MW, _("Unsupported sysfile version: %d. Using version %d instead."), - version, default_version); - - version = default_version; + case SYSFILE_WRITER: + aw->writer = sfm_open_writer (handle, dict, sysfile_opts); + break; + case PORFILE_WRITER: + aw->writer = pfm_open_writer (handle, dict, porfile_opts); + break; } - - dict = dict_clone (default_dict); - start_case_map (dict); - if (!trim_dictionary (dict, OP_SAVE, &compress)) - goto error; - t->map = finish_case_map (dict); - if (t->map != NULL) - case_create (&t->bounce, dict_get_next_value_idx (dict)); - - t->writer = sfm_open_writer (fh, dict, compress, no_name_table); - if (t->writer == NULL) - goto error; - - dict_destroy (dict); - - return t; + + return aw; error: - assert (t != NULL); + any_writer_destroy (aw); dict_destroy (dict); - save_trns_free (&t->h); return NULL; } -/* Parses and performs the SAVE procedure. */ -int -cmd_save (void) +/* Writes case C to writer AW. */ +static void +any_writer_write_case (struct any_writer *aw, struct ccase *c) { - struct save_trns *t = cmd_save_internal (); - if (t != NULL) + if (aw->map != NULL) { - procedure (save_write_case_func, t); - save_trns_free (&t->h); - free(t); - return CMD_SUCCESS; + map_case (aw->map, c, &aw->bounce); + c = &aw->bounce; } - else + + switch (aw->writer_type) + { + case SYSFILE_WRITER: + sfm_write_case (aw->writer, c); + break; + case PORFILE_WRITER: + pfm_write_case (aw->writer, c); + break; + } +} + +/* SAVE and EXPORT. */ + +static int output_proc (struct ccase *, void *); + +/* Parses and performs the SAVE or EXPORT procedure. */ +static int +parse_output_proc (enum writer_type writer_type) +{ + bool retain_unselected; + struct variable *saved_filter_variable; + struct any_writer *aw; + + aw = parse_write_command (writer_type, PROC_CMD, &retain_unselected); + if (aw == NULL) return CMD_FAILURE; + + saved_filter_variable = dict_get_filter (default_dict); + if (retain_unselected) + dict_set_filter (default_dict, NULL); + procedure (output_proc, aw); + dict_set_filter (default_dict, saved_filter_variable); + + any_writer_destroy (aw); + return CMD_SUCCESS; +} + +/* Writes case C to file. */ +static int +output_proc (struct ccase *c, void *aw_) +{ + struct any_writer *aw = aw_; + any_writer_write_case (aw, c); + return 0; } -/* Parses the XSAVE transformation command. */ int -cmd_xsave (void) +cmd_save (void) { - struct save_trns *t = cmd_save_internal (); - if (t != NULL) - { - add_transformation (&t->h); - return CMD_SUCCESS; - } - else - return CMD_FAILURE; + return parse_output_proc (SYSFILE_WRITER); } -/* Writes the given C to the file specified by T. */ -static void -do_write_case (struct save_trns *t, struct ccase *c) +int +cmd_export (void) { - if (t->map == NULL) - sfm_write_case (t->writer, c); - else - { - map_case (t->map, c, &t->bounce); - sfm_write_case (t->writer, &t->bounce); - } + return parse_output_proc (PORFILE_WRITER); } + +/* XSAVE and XEXPORT. */ + +/* Transformation. */ +struct output_trns + { + struct trns_header h; /* Header. */ + struct any_writer *aw; /* Writer. */ + }; + +static trns_proc_func output_trns_proc; +static trns_free_func output_trns_free; -/* Writes case C to the system file specified on SAVE. */ +/* Parses the XSAVE or XEXPORT transformation command. */ static int -save_write_case_func (struct ccase *c, void *aux UNUSED) +parse_output_trns (enum writer_type writer_type) { - do_write_case (aux, c); - return 1; + struct output_trns *t = xmalloc (sizeof *t); + t->h.proc = output_trns_proc; + t->h.free = output_trns_free; + t->aw = parse_write_command (writer_type, XFORM_CMD, NULL); + if (t->aw == NULL) + { + free (t); + return CMD_FAILURE; + } + + add_transformation (&t->h); + return CMD_SUCCESS; } -/* Writes case C to the system file specified on XSAVE. */ +/* Writes case C to the system file specified on XSAVE or XEXPORT. */ static int -save_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED) +output_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED) { - struct save_trns *t = (struct save_trns *) h; - do_write_case (t, c); + struct output_trns *t = (struct output_trns *) h; + any_writer_write_case (t->aw, c); return -1; } -/* Frees a SAVE transformation. */ +/* Frees an XSAVE or XEXPORT transformation. */ static void -save_trns_free (struct trns_header *t_) +output_trns_free (struct trns_header *h) { - struct save_trns *t = (struct save_trns *) t_; + struct output_trns *t = (struct output_trns *) h; - if (t != NULL) + if (t != NULL) { - sfm_close_writer (t->writer); - destroy_case_map (t->map); - case_destroy (&t->bounce); + any_writer_destroy (t->aw); + free (t); } } +/* XSAVE command. */ +int +cmd_xsave (void) +{ + return parse_output_trns (SYSFILE_WRITER); +} + +/* XEXPORT command. */ +int +cmd_xexport (void) +{ + return parse_output_trns (PORFILE_WRITER); +} + static bool rename_variables (struct dictionary *dict); static bool drop_variables (struct dictionary *dict); static bool keep_variables (struct dictionary *dict); @@ -362,65 +533,26 @@ static bool keep_variables (struct dictionary *dict); /* Commands that read and write system files share a great deal of common syntactic structure for rearranging and dropping variables. This function parses this syntax and modifies DICT - appropriately. - - OP is the operation being performed. For operations that - write a system file, *COMPRESS is set to 1 if the system file - should be compressed, 0 otherwise. - - Returns true on success, false on failure. */ + appropriately. Returns true on success, false on failure. */ static bool -trim_dictionary (struct dictionary *dict, enum operation op, int *compress) +parse_dict_trim (struct dictionary *dict) { - assert ((compress != NULL) == (op == OP_SAVE)); - if (get_scompression()) - *compress = 1; - - if (op == OP_SAVE || op == OP_EXPORT) + if (lex_match_id ("MAP")) { - /* Delete all the scratch variables. */ - struct variable **v; - size_t nv; - size_t i; - - v = xmalloc (sizeof *v * dict_get_var_cnt (dict)); - nv = 0; - for (i = 0; i < dict_get_var_cnt (dict); i++) - if (dict_class_from_id (dict_get_var (dict, i)->name) == DC_SCRATCH) - v[nv++] = dict_get_var (dict, i); - dict_delete_vars (dict, v, nv); - free (v); + /* FIXME. */ + return true; } - - while (lex_match ('/')) + else if (lex_match_id ("DROP")) + return drop_variables (dict); + else if (lex_match_id ("KEEP")) + return keep_variables (dict); + else if (lex_match_id ("RENAME")) + return rename_variables (dict); + else { - bool ok = true; - - if (op == OP_SAVE && lex_match_id ("COMPRESSED")) - *compress = 1; - else if (op == OP_SAVE && lex_match_id ("UNCOMPRESSED")) - *compress = 0; - else if (lex_match_id ("DROP")) - ok = drop_variables (dict); - else if (lex_match_id ("KEEP")) - ok = keep_variables (dict); - else if (lex_match_id ("RENAME")) - ok = rename_variables (dict); - else - { - lex_error (_("expecting a valid subcommand")); - ok = false; - } - - if (!ok) - return false; + lex_error (_("expecting a valid subcommand")); + return false; } - - if (!lex_end_of_command ()) - return false; - - dict_compact_values (dict); - return true; } /* Parses and performs the RENAME subcommand of GET and SAVE. */ @@ -484,8 +616,8 @@ rename_variables (struct dictionary *dict) if (nn != nv) { msg (SE, _("Number of variables on left side of `=' (%d) does not " - "match number of variables on right side (%d), in " - "parenthesized group %d of RENAME subcommand."), + "match number of variables on right side (%d), in " + "parenthesized group %d of RENAME subcommand."), nv - old_nv, nn - old_nv, group); goto done; } @@ -501,7 +633,7 @@ rename_variables (struct dictionary *dict) } success = 1; -done: + done: for (i = 0; i < nn; i++) free (new_names[i]); free (new_names); @@ -558,91 +690,6 @@ keep_variables (struct dictionary *dict) return true; } -/* EXPORT procedure. */ -struct export_proc - { - struct pfm_writer *writer; /* System file writer. */ - struct case_map *map; /* Map from active file to system file dict. */ - struct ccase bounce; /* Bounce buffer. */ - }; - -static int export_write_case_func (struct ccase *, void *); -static void export_proc_free (struct export_proc *); - -/* Parses the EXPORT command. */ -/* FIXME: same as cmd_save_internal(). */ -int -cmd_export (void) -{ - struct file_handle *fh; - struct dictionary *dict; - struct export_proc *proc; - - proc = xmalloc (sizeof *proc); - proc->writer = NULL; - proc->map = NULL; - case_nullify (&proc->bounce); - - lex_match ('/'); - if (lex_match_id ("OUTFILE")) - lex_match ('='); - fh = fh_parse (); - if (fh == NULL) - return CMD_FAILURE; - - dict = dict_clone (default_dict); - start_case_map (dict); - if (!trim_dictionary (dict, OP_EXPORT, NULL)) - goto error; - proc->map = finish_case_map (dict); - if (proc->map != NULL) - case_create (&proc->bounce, dict_get_next_value_idx (dict)); - - proc->writer = pfm_open_writer (fh, dict); - if (proc->writer == NULL) - goto error; - - dict_destroy (dict); - - procedure (export_write_case_func, proc); - export_proc_free (proc); - free (proc); - - return CMD_SUCCESS; - - error: - dict_destroy (dict); - export_proc_free (proc); - free (proc); - return CMD_FAILURE; -} - -/* Writes case C to the EXPORT file. */ -static int -export_write_case_func (struct ccase *c, void *aux) -{ - struct export_proc *proc = aux; - if (proc->map == NULL) - pfm_write_case (proc->writer, c); - else - { - map_case (proc->map, c, &proc->bounce); - pfm_write_case (proc->writer, &proc->bounce); - } - return 1; -} - -static void -export_proc_free (struct export_proc *proc) -{ - if (proc != NULL) - { - pfm_close_writer (proc->writer); - destroy_case_map (proc->map); - case_destroy (&proc->bounce); - } -} - /* MATCH FILES. */ #include "debug-print.h" @@ -657,8 +704,7 @@ enum /* One of the files on MATCH FILES. */ struct mtf_file { - struct mtf_file *next, *prev; - /* Next, previous in the list of files. */ + struct mtf_file *next, *prev; /* Next, previous in the list of files. */ struct mtf_file *next_min; /* Next in the chain of minimums. */ int type; /* One of MTF_*. */ @@ -680,7 +726,7 @@ struct mtf_proc struct mtf_file *head; /* First file mentioned on FILE or TABLE. */ struct mtf_file *tail; /* Last file mentioned on FILE or TABLE. */ - size_t by_cnt; /* Number of variables on BY subcommand. */ + int by_cnt; /* Number of variables on BY subcommand. */ /* Names of FIRST, LAST variables. */ char first[LONG_NAME_LEN + 1], last[LONG_NAME_LEN + 1]; @@ -1054,7 +1100,7 @@ cmd_match_files (void) mtf_free (&mtf); return CMD_SUCCESS; -error: + error: mtf_free (&mtf); return CMD_FAILURE; } @@ -1474,26 +1520,20 @@ cmd_import (void) struct import_pgm *pgm = NULL; struct file_handle *fh = NULL; struct dictionary *dict = NULL; - int type; - - pgm = xmalloc (sizeof *pgm); - pgm->reader = NULL; - pgm->map = NULL; - case_nullify (&pgm->bounce); + enum pfm_type type; + lex_match ('/'); for (;;) { - lex_match ('/'); - - if (lex_match_id ("FILE") || token == T_STRING) + if (pgm == NULL && (lex_match_id ("FILE") || token == T_STRING)) { lex_match ('='); fh = fh_parse (); if (fh == NULL) - return CMD_FAILURE; + goto error; } - else if (lex_match_id ("TYPE")) + else if (pgm == NULL && lex_match_id ("TYPE")) { lex_match ('='); @@ -1504,27 +1544,48 @@ cmd_import (void) else { lex_error (_("expecting COMM or TAPE")); - return CMD_FAILURE; + goto error; } } - else break; + else + { + if (pgm == NULL) + { + if (fh == NULL) + { + lex_sbc_missing ("FILE"); + goto error; + } + + discard_variables (); + + pgm = xmalloc (sizeof *pgm); + pgm->reader = pfm_open_reader (fh, &dict, NULL); + pgm->map = NULL; + case_nullify (&pgm->bounce); + if (pgm->reader == NULL) + goto error; + + case_create (&pgm->bounce, dict_get_next_value_idx (dict)); + + start_case_map (dict); + } + + if (token == '.') + break; + + if (!parse_dict_trim (dict)) + goto error; + } + + lex_match ('/'); } - if (!lex_match ('/') && token != '.') + if (pgm == NULL) { lex_error (NULL); - return CMD_FAILURE; + goto error; } - discard_variables (); - - pgm->reader = pfm_open_reader (fh, &dict, NULL); - if (pgm->reader == NULL) - return CMD_FAILURE; - case_create (&pgm->bounce, dict_get_next_value_idx (dict)); - - start_case_map (dict); - if (!trim_dictionary (dict, OP_READ, NULL)) - goto error; pgm->map = finish_case_map (dict); dict_destroy (default_dict); @@ -1619,7 +1680,7 @@ struct case_map at will before using finish_case_map() to produce the case map. - Uses D's aux members, which may not otherwise be in use. */ + Uses D's aux members, which must otherwise not be in use. */ static void start_case_map (struct dictionary *d) { diff --git a/src/lexer.c b/src/lexer.c index fe999ff1..8a3ccc21 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -384,6 +384,22 @@ lex_get (void) #endif } +/* Reports an error to the effect that subcommand SBC may only be + specified once. */ +void +lex_sbc_only_once (const char *sbc) +{ + msg (SE, _("Subcommand %s may only be specified once."), sbc); +} + +/* Reports an error to the effect that subcommand SBC is + missing. */ +void +lex_sbc_missing (const char *sbc) +{ + lex_error (_("missing required subcommand %s"), sbc); +} + /* Prints a syntax error message containing the current token and given message MESSAGE (if non-null). */ void diff --git a/src/lexer.h b/src/lexer.h index af3aeb0a..816b4317 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -87,6 +87,8 @@ void lex_done (void); /* Common functions. */ void lex_get (void); void lex_error (const char *, ...); +void lex_sbc_only_once (const char *); +void lex_sbc_missing (const char *); int lex_end_of_command (void); /* Token testing functions. */ diff --git a/src/pfm-read.h b/src/pfm-read.h index 5346f153..3f84e160 100644 --- a/src/pfm-read.h +++ b/src/pfm-read.h @@ -24,13 +24,6 @@ #include -/* Portable file types. */ -enum pfm_type - { - PFM_COMM, /* Formatted for communication. */ - PFM_TAPE /* Formatted for tape. */ - }; - /* Information produced by pfm_read_dictionary() that doesn't fit into a dictionary struct. */ struct pfm_read_info diff --git a/src/pfm-write.c b/src/pfm-write.c index fbe56f6a..cd088b7b 100644 --- a/src/pfm-write.c +++ b/src/pfm-write.c @@ -22,11 +22,14 @@ #include "error.h" #include #include +#include #include #include #include #include +#include #include +#include #include "alloc.h" #include "case.h" #include "dictionary.h" @@ -35,6 +38,7 @@ #include "hash.h" #include "magic.h" #include "misc.h" +#include "stat-macros.h" #include "str.h" #include "value-labels.h" #include "var.h" @@ -55,6 +59,8 @@ struct pfm_writer size_t var_cnt; /* Number of variables. */ struct pfm_var *vars; /* Variables. */ + + int digits; /* Digits of precision. */ }; /* A variable to write to the portable file. */ @@ -73,36 +79,55 @@ static int write_value_labels (struct pfm_writer *, const struct dictionary *); static void format_trig_double (long double, int base_10_precision, char[]); static char *format_trig_int (int, bool force_sign, char[]); -/* Writes the dictionary DICT to portable file HANDLE. Returns - nonzero only if successful. DICT will not be modified, except - to assign short names. */ +/* Returns default options for writing a portable file. */ +struct pfm_write_options +pfm_writer_default_options (void) +{ + struct pfm_write_options opts; + opts.create_writeable = true; + opts.type = PFM_COMM; + opts.digits = DBL_DIG; + return opts; +} + +/* Writes the dictionary DICT to portable file HANDLE according + to the given OPTS. Returns nonzero only if successful. DICT + will not be modified, except to assign short names. */ struct pfm_writer * -pfm_open_writer (struct file_handle *fh, struct dictionary *dict) +pfm_open_writer (struct file_handle *fh, struct dictionary *dict, + struct pfm_write_options opts) { struct pfm_writer *w = NULL; + mode_t mode; + int fd; size_t i; + /* Create file. */ + mode = S_IRUSR | S_IRGRP | S_IROTH; + if (opts.create_writeable) + mode |= S_IWUSR | S_IWGRP | S_IWOTH; + fd = open (handle_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd < 0) + goto open_error; + + /* Open file handle. */ if (!fh_open (fh, "portable file", "we")) goto error; - - /* Open the physical disk file. */ + + /* Initialize data structures. */ w = xmalloc (sizeof *w); w->fh = fh; - w->file = fopen (handle_get_filename (fh), "wb"); + w->file = fdopen (fd, "w"); + if (w->file == NULL) + { + close (fd); + goto open_error; + } + w->lc = 0; w->var_cnt = 0; w->vars = NULL; - /* Check that file create succeeded. */ - if (w->file == NULL) - { - msg (ME, _("An error occurred while opening \"%s\" for writing " - "as a portable file: %s."), - handle_get_filename (fh), strerror (errno)); - err_cond_fail (); - goto error; - } - w->var_cnt = dict_get_var_cnt (dict); w->vars = xmalloc (sizeof *w->vars * w->var_cnt); for (i = 0; i < w->var_cnt; i++) @@ -113,6 +138,14 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict) pv->fv = dv->fv; } + w->digits = opts.digits; + if (w->digits < 1) + { + msg (ME, _("Invalid decimal digits count %d. Treating as %d."), + w->digits, DBL_DIG); + w->digits = DBL_DIG; + } + /* Write file header. */ if (!write_header (w) || !write_version_data (w) @@ -123,9 +156,16 @@ pfm_open_writer (struct file_handle *fh, struct dictionary *dict) return w; -error: + error: pfm_close_writer (w); return NULL; + + open_error: + msg (ME, _("An error occurred while opening \"%s\" for writing " + "as a portable file: %s."), + handle_get_filename (fh), strerror (errno)); + err_cond_fail (); + goto error; } /* Write NBYTES starting at BUF to the portable file represented by @@ -169,7 +209,7 @@ static int write_float (struct pfm_writer *w, double d) { char buffer[64]; - format_trig_double (d, DBL_DIG, buffer); + format_trig_double (d, floor (d) == d ? DBL_DIG : w->digits, buffer); return buf_write (w, buffer, strlen (buffer)) && buf_write (w, "/", 1); } @@ -754,6 +794,8 @@ format_trig_double (long double value, int base_10_precision, char output[]) required base-30 precision as 2/3 of the base-10 precision (log30(10) = .68). */ assert (base_10_precision > 0); + if (base_10_precision > LDBL_DIG) + base_10_precision = LDBL_DIG; base_30_precision = DIV_RND_UP (base_10_precision * 2, 3); if (trig_cnt > base_30_precision) { diff --git a/src/pfm-write.h b/src/pfm-write.h index 3f82038d..e335098a 100644 --- a/src/pfm-write.h +++ b/src/pfm-write.h @@ -20,12 +20,32 @@ #ifndef PFM_WRITE_H #define PFM_WRITE_H +#include + /* Portable file writing. */ +/* Portable file types. */ +enum pfm_type + { + PFM_COMM, /* Formatted for communication. */ + PFM_TAPE /* Formatted for tape. */ + }; + +/* Portable file writing options. */ +struct pfm_write_options + { + bool create_writeable; /* File perms: writeable or read/only? */ + enum pfm_type type; /* Type of portable file (TODO). */ + int digits; /* Digits of precision. */ + }; + struct file_handle; struct dictionary; struct ccase; -struct pfm_writer *pfm_open_writer (struct file_handle *, struct dictionary *); +struct pfm_writer *pfm_open_writer (struct file_handle *, struct dictionary *, + struct pfm_write_options); +struct pfm_write_options pfm_writer_default_options (void); + int pfm_write_case (struct pfm_writer *, struct ccase *); void pfm_close_writer (struct pfm_writer *); diff --git a/src/sfm-write.c b/src/sfm-write.c index 80b78408..4f0f9ec1 100644 --- a/src/sfm-write.c +++ b/src/sfm-write.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #if HAVE_UNISTD_H #include /* Required by SunOS4. */ @@ -37,6 +39,8 @@ #include "hash.h" #include "magic.h" #include "misc.h" +#include "settings.h" +#include "stat-macros.h" #include "str.h" #include "value-labels.h" #include "var.h" @@ -107,33 +111,61 @@ var_flt64_cnt (const struct variable *v) return v->type == NUMERIC ? 1 : DIV_RND_UP (v->width, sizeof (flt64)); } +/* Returns default options for writing a system file. */ +struct sfm_write_options +sfm_writer_default_options (void) +{ + struct sfm_write_options opts; + opts.create_writeable = true; + opts.compress = get_scompression (); + opts.version = 3; + return opts; +} + /* Opens the system file designated by file handle FH for writing - cases from dictionary D. If COMPRESS is nonzero, the - system file will be compressed. If OMIT_LONG_NAMES is nonzero, the - long name table will be omitted. + cases from dictionary D according to the given OPTS. If + COMPRESS is nonzero, the system file will be compressed. No reference to D is retained, so it may be modified or destroyed at will after this function returns. D is not modified by this function, except to assign short names. */ struct sfm_writer * -sfm_open_writer (struct file_handle *fh, - struct dictionary *d, int compress, - short omit_long_names) +sfm_open_writer (struct file_handle *fh, struct dictionary *d, + struct sfm_write_options opts) { struct sfm_writer *w = NULL; + mode_t mode; + int fd; int idx; int i; + /* Check version. */ + if (opts.version != 2 && opts.version != 3) + { + msg (ME, _("Unknown system file version %d. Treating as version %d."), + opts.version, 3); + opts.version = 3; + } + + /* Create file. */ + mode = S_IRUSR | S_IRGRP | S_IROTH; + if (opts.create_writeable) + mode |= S_IWUSR | S_IWGRP | S_IWOTH; + fd = open (handle_get_filename (fh), O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd < 0) + goto open_error; + + /* Open file handle. */ if (!fh_open (fh, "system file", "we")) goto error; /* Create and initialize writer. */ w = xmalloc (sizeof *w); w->fh = fh; - w->file = fopen (handle_get_filename (fh), "wb"); + w->file = fdopen (fd, "w"); w->needs_translation = does_dict_need_translation (d); - w->compress = compress; + w->compress = opts.compress; w->case_cnt = 0; w->flt64_cnt = 0; @@ -152,13 +184,10 @@ sfm_open_writer (struct file_handle *fh, } /* Check that file create succeeded. */ - if (w->file == NULL) + if (w->file == NULL) { - msg (ME, _("Error opening \"%s\" for writing " - "as a system file: %s."), - handle_get_filename (w->fh), strerror (errno)); - err_cond_fail (); - goto error; + close (fd); + goto open_error; } /* Write the file header. */ @@ -189,7 +218,7 @@ sfm_open_writer (struct file_handle *fh, if (!write_variable_display_parameters (w, d)) goto error; - if (!omit_long_names) + if (opts.version >= 3) { if (!write_longvar_table (w, d)) goto error; @@ -225,6 +254,12 @@ sfm_open_writer (struct file_handle *fh, error: sfm_close_writer (w); return NULL; + + open_error: + msg (ME, _("Error opening \"%s\" for writing as a system file: %s."), + handle_get_filename (w->fh), strerror (errno)); + err_cond_fail (); + goto error; } static int diff --git a/src/sfm-write.h b/src/sfm-write.h index dfddd4f6..896353db 100644 --- a/src/sfm-write.h +++ b/src/sfm-write.h @@ -20,13 +20,24 @@ #ifndef SFM_WRITE_H #define SFM_WRITE_H 1 +#include + /* Writing system files. */ +/* Options for creating a system file. */ +struct sfm_write_options + { + bool create_writeable; /* File perms: writeable or read/only? */ + bool compress; /* Compress file? */ + int version; /* System file version (currently 2 or 3). */ + }; + struct file_handle; struct dictionary; struct ccase; -struct sfm_writer *sfm_open_writer (struct file_handle *, struct dictionary *, - int compress, short omit_longnames); +struct sfm_writer *sfm_open_writer (struct file_handle *, struct dictionary *, + struct sfm_write_options); +struct sfm_write_options sfm_writer_default_options (void); int sfm_write_case (struct sfm_writer *, struct ccase *); void sfm_close_writer (struct sfm_writer *); diff --git a/tests/ChangeLog b/tests/ChangeLog index f5151193..0b5d0de2 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,9 @@ +Sun Aug 21 00:20:02 2005 Ben Pfaff + + * command/import-export.sh: Simplify. + + * command/sysfiles-old.sh: Use version 2, not 3x. + Sat Aug 6 17:32:39 2005 Ben Pfaff * command/missing-values.sh: New test. diff --git a/tests/command/import-export.sh b/tests/command/import-export.sh index 4476fa7d..74a27543 100755 --- a/tests/command/import-export.sh +++ b/tests/command/import-export.sh @@ -50,7 +50,7 @@ cd $TEMPDIR activity="create program" cat > $TESTFILE <