2 * Program: pgn-extract: a Portable Game Notation (PGN) extractor.
3 * Copyright (C) 1994-2014 David Barnes
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 1, or (at your option)
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 * David Barnes may be contacted as D.J.Barnes@kent.ac.uk
19 * http://www.cs.kent.ac.uk/people/staff/djb/
44 #define CURRENT_VERSION "v17-19"
45 #define URL "http://www.cs.kent.ac.uk/people/staff/djb/pgn-extract/"
47 /* The prefix of the arguments allowed in an argsfile.
50 * where ? is an argument letter.
54 * means use filename as a NORMALFILE source of games.
56 * A line with no leading colon character is taken to apply to the
57 * move-reason argument line. Currently, this only applies to the
61 static const char argument_prefix[] = ":-";
62 static const int argument_prefix_len = sizeof(argument_prefix)-1;
63 static ArgType classify_arg(const char *line);
65 /* Select the correct function according to operating system. */
67 stringcompare(const char *s1, const char *s2)
69 #if defined(__unix__) || defined(__linux__) || defined(__APPLE__)
70 return strcasecmp(s1,s2);
72 return _stricmp(s1,s2);
77 /* Return TRUE if str contains prefix as a prefix, FALSE otherwise. */
79 prefixMatch(const char *prefix, const char *str)
81 size_t prefixLen = strlen(prefix);
82 if(strlen(str) >= prefixLen) {
83 #if defined(__unix__) || defined(__linux__)
84 return strncasecmp(prefix, str, prefixLen) == 0;
86 return _strnicmp(prefix, str, prefixLen) == 0;
95 /* Skip over leading spaces from the string. */
96 static const char *skip_leading_spaces(const char *str)
104 /* Print a usage message, and exit. */
108 const char *help_data[] = {
109 "-7 -- output only the seven tag roster for each game. Other tags (apart",
110 " from FEN and possibly ECO) are discarded (See -e).",
111 "-#num -- output num games per file, to files named 1.pgn, 2.pgn, etc.",
115 "-aoutputfile -- append extracted games to outputfile. (See -o).",
116 "-Aargsfile -- read the program's arguments from argsfile.",
117 "-b[elu]num -- restricted bounds on the number of moves in a game.",
118 " lnum set a lower bound of `num' moves,",
119 " unum set an upper bound of `num' moves,",
120 " otherwise num (or enum) means equal-to `num' moves.",
121 "-cfile[.pgn] -- Use file.pgn as a check-file for duplicates or",
122 " contents of file (no pgn suffix) as a list of check-file names.",
123 "-C -- don't include comments in the output. Ordinarily these are retained.",
124 "-dduplicates -- write duplicate games to the file duplicates.",
125 "-D -- don't output duplicate games.",
126 "-eECO_file -- perform ECO classification of games. The optional",
127 " ECO_file should contain a PGN format list of ECO lines",
128 " Default is to use eco.pgn from the current directory.",
129 "-E[123 etc.] -- split output into separate files according to ECO.",
130 " E1 : Produce files from ECO letter, A.pgn, B.pgn, ...",
131 " E2 : Produce files from ECO letter and first digit, A0.pgn, ...",
132 " E3 : Produce files from full ECO code, A00.pgn, A01.pgn, ...",
133 " Further digits may be used to produce non-standard further",
134 " refined division of games.",
135 " All files are opened in append mode.",
136 "-F -- output a FEN string comment of the final game position.",
137 "-ffile_list -- file_list contains the list of PGN source files, one per line.",
138 "-h -- print details of the arguments.",
139 "-llogfile -- Save the diagnostics in logfile rather than using stderr.",
140 "-Llogfile -- Append all diagnostics to logfile, rather than overwriting.",
141 "-M -- Match only games which end in checkmate.",
142 "-noutputfile -- Write all valid games not otherwise output to outputfile.",
143 "-N -- don't include NAGs in the output. Ordinarily these are retained.",
144 "-ooutputfile -- write extracted games to outputfile (existing contents lost).",
145 "-P -- don't match permutations of the textual variations (-v).",
146 "-Rtagorder -- Use the tag ordering specified in the file tagorder.",
147 "-r -- report any errors but don't extract.",
148 "-S -- Use a simple soundex algorithm for some tag matches. If used",
149 " this option must precede the -t or -T options.",
150 "-s -- silent mode: don't report each game as it is extracted.",
151 "-ttagfile -- file of player, date, result or FEN extraction criteria.",
152 "-Tcriterion -- player, date, or result extraction criterion.",
153 "-U -- don't output games that only occur once. (See -d).",
154 "-vvariations -- the file variations contains the textual lines of interest.",
155 "-V -- don't include variations in the output. Ordinarily these are retained.",
156 "-wwidth -- set width as an approximate line width for output.",
157 "-W[cm|epd|halg|lalg|elalg|san] -- specify the output format to use.",
159 " -W means use the input format.",
160 " -Wcm is (a possibly obsolete) ChessMaster format.",
161 " -Wepd is EPD format.",
162 " -Wsan[PNBRQK] for language specific output.",
163 " -Whalg is hyphenated long algebraic.",
164 " -Wlalg is long algebraic.",
165 " -Welalg is enhanced long algebraic.",
166 " -Wuci is output compatible with the UCI protocol.",
167 "-xvariations -- the file variations contains the lines resulting in",
168 " positions of interest.",
169 "-zendings -- the file endings contains the end positions of interest.",
170 "-Z -- use the file virtual.tmp as an external hash table for duplicates.",
171 " Use when MallocOrDie messages occur with big datasets.",
175 "--addhashcode - output a HashCode tag",
177 "--checkfile - see -c",
178 "--checkmate - see -M",
179 "--dumpeco - dump a list of all ECO hashes with names, then quit",
180 "--duplicates - see -d",
181 "--evaluation - include a position evaluation after each move",
182 "--fencomments - include a FEN string after each move",
183 "--fuzzydepth plies - positional duplicates match",
185 "--keepbroken - retain games with errors",
186 "--linelength - see -w",
187 "--markmatches - mark positional and material matches with a comment; see -t, -v, and -z",
188 "--nochecks - don't output + and # after moves.",
189 "--nocomments - see -C",
190 "--noduplicates - see -D",
191 "--nomovenumbers - don't output move numbers.",
193 "--noresults - don't output results.",
194 "--notags - don't output any tags.",
195 "--nounique - see -U",
198 "--plylimit - limit the number of plies output.",
199 "--selectonly N - only output the Nth matched game (N > 0)",
201 "--stalemate - only output games that end in stalemate.",
202 "--totalplycount - include a tag with the total number of plies in a game.",
203 "--version - print the current version number and exit.",
205 /* Must be NULL terminated. */
209 const char **data = help_data;
211 fprintf(GlobalState.logfile,
212 "pgn-extract %s (%s): a Portable Game Notation (PGN) manipulator.\n",
213 CURRENT_VERSION,__DATE__);
214 fprintf(GlobalState.logfile,
215 "Copyright (C) 1994-2014 David J. Barnes (d.j.barnes@kent.ac.uk)\n");
216 fprintf(GlobalState.logfile,"%s\n\n",URL);
217 fprintf(GlobalState.logfile,"Usage: pgn-extract [arguments] [file.pgn ...]\n");
219 for(; *data != NULL; data++){
220 fprintf(GlobalState.logfile,"%s\n",*data);
226 read_args_file(const char *infile)
228 FILE *fp = fopen(infile,"r");
231 fprintf(GlobalState.logfile,"Cannot open %s for reading.\n",infile);
235 ArgType linetype = NO_ARGUMENT_MATCH;
237 while((line = read_line(fp)) != NULL){
238 if(blank_line(line)){
242 nexttype = classify_arg(line);
243 if(nexttype == NO_ARGUMENT_MATCH){
244 if(*line == argument_prefix[0]){
245 /* Treat the line as a source file name. */
246 add_filename_to_source_list(&line[1],NORMALFILE);
248 else if(linetype != NO_ARGUMENT_MATCH){
249 /* Handle the line. */
252 add_textual_variation_from_line(line);
254 case POSITIONS_ARGUMENT:
255 add_positional_variation_from_line(line);
258 process_tag_line(infile,line);
260 case TAG_ROSTER_ARGUMENT:
261 process_roster_line(line);
263 case ENDINGS_ARGUMENT:
264 process_ending_line(line);
268 fprintf(GlobalState.logfile,
269 "Internal error: unknown linetype %d in read_args_file\n",
276 /* It should have been a line applying to the
279 fprintf(GlobalState.logfile,
280 "Missing argument type for line %s in the argument file.\n",
287 /* Arguments with a possible additional
289 * All of these apply only to the current
290 * line in the argument file.
292 case WRITE_TO_OUTPUT_FILE_ARGUMENT:
293 case APPEND_TO_OUTPUT_FILE_ARGUMENT:
294 case WRITE_TO_LOG_FILE_ARGUMENT:
295 case APPEND_TO_LOG_FILE_ARGUMENT:
296 case DUPLICATES_FILE_ARGUMENT:
297 case USE_ECO_FILE_ARGUMENT:
298 case CHECK_FILE_ARGUMENT:
299 case FILE_OF_FILES_ARGUMENT:
300 case BOUNDS_ARGUMENT:
301 case GAMES_PER_FILE_ARGUMENT:
302 case ECO_OUTPUT_LEVEL_ARGUMENT:
303 case FILE_OF_ARGUMENTS_ARGUMENT:
304 case NON_MATCHING_GAMES_ARGUMENT:
305 case TAG_EXTRACTION_ARGUMENT:
306 case LINE_WIDTH_ARGUMENT:
307 case OUTPUT_FORMAT_ARGUMENT:
308 process_argument(line[argument_prefix_len],
309 &line[argument_prefix_len+1]);
310 linetype = NO_ARGUMENT_MATCH;
312 case LONG_FORM_ARGUMENT:
314 char *arg = &line[argument_prefix_len+1];
315 char *space = strchr(arg, ' ');
317 /* We need to drop an associated value from arg. */
318 int arglen = space - arg;
319 char *just_arg = (char *) MallocOrDie(arglen + 1);
320 strncpy(just_arg, arg, arglen);
321 just_arg[arglen] = '\0';
322 process_long_form_argument(just_arg,
323 skip_leading_spaces(space));
326 process_long_form_argument(arg, "");
327 linetype = NO_ARGUMENT_MATCH;
332 /* Arguments with no additional
334 * All of these apply only to the current
335 * line in the argument file.
337 case SEVEN_TAG_ROSTER_ARGUMENT:
339 case ALTERNATIVE_HELP_ARGUMENT:
340 case DONT_KEEP_COMMENTS_ARGUMENT:
341 case DONT_KEEP_DUPLICATES_ARGUMENT:
342 case DONT_MATCH_PERMUTATIONS_ARGUMENT:
343 case DONT_KEEP_NAGS_ARGUMENT:
344 case OUTPUT_FEN_STRING_ARGUMENT:
345 case CHECK_ONLY_ARGUMENT:
346 case KEEP_SILENT_ARGUMENT:
347 case USE_SOUNDEX_ARGUMENT:
348 case MATCH_CHECKMATE_ARGUMENT:
349 case SUPPRESS_ORIGINALS_ARGUMENT:
350 case DONT_KEEP_VARIATIONS_ARGUMENT:
351 case USE_VIRTUAL_HASH_TABLE_ARGUMENT:
352 process_argument(line[argument_prefix_len],"");
353 linetype = NO_ARGUMENT_MATCH;
356 /* Arguments whose values persist beyond
360 case POSITIONS_ARGUMENT:
361 case ENDINGS_ARGUMENT:
363 case TAG_ROSTER_ARGUMENT:
364 process_argument(line[argument_prefix_len],
365 &line[argument_prefix_len+1]);
366 /* Apply this type to subsequent lines. */
380 /* Determine which (if any) type of argument is
381 * indicated by the contents of the current line.
382 * Arguments are assumed to be surrounded by
386 classify_arg(const char *line)
388 /* Valid arguments must have at least one character beyond
391 static const size_t min_argument_length = 1+sizeof(argument_prefix)-1;
392 size_t line_length = strlen(line);
394 /* Check for a line of the form:
397 if((strncmp(line,argument_prefix,argument_prefix_len) == 0) &&
398 (line_length >= min_argument_length)){
399 char argument_letter = line[argument_prefix_len];
400 switch(argument_letter){
403 case POSITIONS_ARGUMENT:
404 case ENDINGS_ARGUMENT:
405 case TAG_EXTRACTION_ARGUMENT:
406 case LINE_WIDTH_ARGUMENT:
407 case OUTPUT_FORMAT_ARGUMENT:
408 case SEVEN_TAG_ROSTER_ARGUMENT:
409 case FILE_OF_ARGUMENTS_ARGUMENT:
410 case NON_MATCHING_GAMES_ARGUMENT:
411 case DONT_KEEP_COMMENTS_ARGUMENT:
412 case DONT_KEEP_DUPLICATES_ARGUMENT:
413 case DONT_KEEP_NAGS_ARGUMENT:
414 case DONT_MATCH_PERMUTATIONS_ARGUMENT:
415 case OUTPUT_FEN_STRING_ARGUMENT:
416 case CHECK_ONLY_ARGUMENT:
417 case KEEP_SILENT_ARGUMENT:
418 case USE_SOUNDEX_ARGUMENT:
419 case MATCH_CHECKMATE_ARGUMENT:
420 case SUPPRESS_ORIGINALS_ARGUMENT:
421 case DONT_KEEP_VARIATIONS_ARGUMENT:
422 case WRITE_TO_OUTPUT_FILE_ARGUMENT:
423 case WRITE_TO_LOG_FILE_ARGUMENT:
424 case APPEND_TO_LOG_FILE_ARGUMENT:
425 case APPEND_TO_OUTPUT_FILE_ARGUMENT:
426 case DUPLICATES_FILE_ARGUMENT:
427 case USE_ECO_FILE_ARGUMENT:
428 case CHECK_FILE_ARGUMENT:
429 case FILE_OF_FILES_ARGUMENT:
430 case BOUNDS_ARGUMENT:
431 case GAMES_PER_FILE_ARGUMENT:
432 case ECO_OUTPUT_LEVEL_ARGUMENT:
434 case ALTERNATIVE_HELP_ARGUMENT:
435 case TAG_ROSTER_ARGUMENT:
436 case LONG_FORM_ARGUMENT:
437 return (ArgType) argument_letter;
439 fprintf(GlobalState.logfile,
440 "Unrecognized argument: %s in the argument file.\n",
443 return NO_ARGUMENT_MATCH;
447 return NO_ARGUMENT_MATCH;
451 /* Process the argument character and its associated value.
452 * This function processes arguments from the command line and
453 * from an argument file associated with the -A argument.
455 * An argument -ofile.pgn would be passed in as:
456 * 'o' and "file.pgn".
457 * A zero-length string for associated_value is not necessarily
458 * an error, e.g. -e has an optional following filename.
459 * NB: If the associated_value is to be used beyond this function,
463 process_argument(char arg_letter,const char *associated_value)
464 { /* Provide an alias for associated_value because it will
465 * often represent a file name.
467 const char *filename = skip_leading_spaces(associated_value);
470 case WRITE_TO_OUTPUT_FILE_ARGUMENT:
471 case APPEND_TO_OUTPUT_FILE_ARGUMENT:
472 if(GlobalState.ECO_level > 0){
473 fprintf(GlobalState.logfile,"-%c conflicts with -E\n",
476 else if(GlobalState.games_per_file > 0){
477 fprintf(GlobalState.logfile,"-%c conflicts with -#\n",
480 else if(GlobalState.output_filename != NULL){
481 fprintf(GlobalState.logfile,
482 "-%c: File %s has already been selected for output.\n",
483 arg_letter,GlobalState.output_filename);
486 else if(*filename == '\0'){
487 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",arg_letter);
491 if(GlobalState.outputfile != NULL){
492 (void) fclose(GlobalState.outputfile);
494 if(arg_letter == WRITE_TO_OUTPUT_FILE_ARGUMENT){
495 GlobalState.outputfile = must_open_file(filename,"w");
498 GlobalState.outputfile = must_open_file(filename,"a");
500 GlobalState.output_filename = filename;
503 case WRITE_TO_LOG_FILE_ARGUMENT:
504 case APPEND_TO_LOG_FILE_ARGUMENT:
505 /* Take precautions against multiple log files. */
506 if((GlobalState.logfile != stderr) && (GlobalState.logfile != NULL)){
507 (void) fclose(GlobalState.logfile);
509 if(arg_letter == WRITE_TO_LOG_FILE_ARGUMENT){
510 GlobalState.logfile = fopen(filename,"w");
513 GlobalState.logfile = fopen(filename,"a");
515 if(GlobalState.logfile == NULL){
516 fprintf(stderr,"Unable to open %s for writing.\n",filename);
517 GlobalState.logfile = stderr;
520 case DUPLICATES_FILE_ARGUMENT:
521 if(*filename == '\0'){
522 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",arg_letter);
525 else if(GlobalState.suppress_duplicates){
526 fprintf(GlobalState.logfile,
527 "-%c clashes with the -%c flag.\n",arg_letter,
528 DONT_KEEP_DUPLICATES_ARGUMENT);
532 GlobalState.duplicate_file = must_open_file(filename,"w");
535 case USE_ECO_FILE_ARGUMENT:
536 GlobalState.add_ECO = TRUE;
537 if(*filename != '\0'){
538 GlobalState.eco_file = copy_string(filename);
540 else if((filename = getenv("ECO_FILE")) != NULL){
541 GlobalState.eco_file = filename;
544 /* Use the default which is already set up. */
548 case ECO_OUTPUT_LEVEL_ARGUMENT:
551 if(GlobalState.output_filename != NULL){
552 fprintf(GlobalState.logfile,
553 "-%c: File %s has already been selected for output.\n",
555 GlobalState.output_filename);
558 else if(GlobalState.games_per_file > 0){
559 fprintf(GlobalState.logfile,
560 "-%c conflicts with -#.\n",
564 else if(sscanf(associated_value,"%u",&level) != 1){
565 fprintf(GlobalState.logfile,
566 "-%c requires a number attached, e.g., -%c1.\n",
567 arg_letter,arg_letter);
570 else if((level < MIN_ECO_LEVEL) || (level > MAX_ECO_LEVEL)){
571 fprintf(GlobalState.logfile,
572 "-%c level should be between %u and %u.\n",
573 MIN_ECO_LEVEL,MAX_ECO_LEVEL,arg_letter);
577 GlobalState.ECO_level = level;
581 case CHECK_FILE_ARGUMENT:
582 if(*filename != '\0'){
583 /* See if it is a single PGN file, or a list
586 size_t len = strlen(filename);
587 /* Check for a .PGN suffix. */
588 const char *suffix = output_file_suffix(SAN);
590 if((len > strlen(suffix)) &&
591 (stringcompare(&filename[len-strlen(suffix)],
593 add_filename_to_source_list(filename,CHECKFILE);
596 FILE *fp = must_open_file(filename,"r");
597 add_filename_list_from_file(fp,CHECKFILE);
602 case FILE_OF_FILES_ARGUMENT:
603 if(*filename != '\0'){
604 FILE *fp = must_open_file(filename,"r");
605 add_filename_list_from_file(fp,NORMALFILE);
609 fprintf(GlobalState.logfile,"Filename expected with -%c\n",
613 case BOUNDS_ARGUMENT:
615 /* Bounds on the number of moves are to be found.
616 * "l#" means less-than-or-equal-to.
617 * "g#" means greater-than-or-equal-to.
618 * Otherwise "#" (or "e#") means that number.
620 /* Equal by default. */
624 const char *bound = associated_value;
634 if(!isdigit((int) *bound)){
635 fprintf(GlobalState.logfile,
636 "-%c must be followed by e, l, or u.\n",
642 if(Ok && (sscanf(bound,"%u",&value) == 1)){
643 GlobalState.check_move_bounds = TRUE;
646 GlobalState.lower_move_bound = value;
647 GlobalState.upper_move_bound = value;
650 if(value <= GlobalState.upper_move_bound){
651 GlobalState.lower_move_bound = value;
654 fprintf(GlobalState.logfile,
655 "Lower bound is greater than the upper bound; -%c ignored.\n",
661 if(value >= GlobalState.lower_move_bound){
662 GlobalState.upper_move_bound = value;
665 fprintf(GlobalState.logfile,
666 "Upper bound is smaller than the lower bound; -%c ignored.\n",
674 fprintf(GlobalState.logfile,
675 "-%c should be in the form -%c[elu]number.\n",
676 arg_letter,arg_letter);
684 case GAMES_PER_FILE_ARGUMENT:
685 if(GlobalState.ECO_level > 0){
686 fprintf(GlobalState.logfile,
687 "-%c conflicts with -E.\n",arg_letter);
690 else if(GlobalState.output_filename != NULL){
691 fprintf(GlobalState.logfile,
692 "-%c: File %s has already been selected for output.\n",
694 GlobalState.output_filename);
697 else if(sscanf(associated_value,"%u",
698 &GlobalState.games_per_file) != 1){
699 fprintf(GlobalState.logfile,
700 "-%c should be followed by an unsigned integer.\n",
708 case FILE_OF_ARGUMENTS_ARGUMENT:
709 if(*filename != '\0'){
710 /* @@@ Potentially recursive call. Is this safe? */
711 read_args_file(filename);
714 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",
718 case NON_MATCHING_GAMES_ARGUMENT:
719 if(*filename != '\0'){
720 if(GlobalState.non_matching_file != NULL){
721 (void) fclose(GlobalState.non_matching_file);
723 GlobalState.non_matching_file = must_open_file(filename,"w");
726 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",arg_letter);
730 case TAG_EXTRACTION_ARGUMENT:
731 /* A single tag extraction criterion. */
732 extract_tag_argument(associated_value);
734 case LINE_WIDTH_ARGUMENT:
735 { /* Specify an output line width. */
738 if(sscanf(associated_value,"%u",&length) > 0){
739 set_output_line_length(length);
742 fprintf(GlobalState.logfile,
743 "-%c should be followed by an unsigned integer.\n",
752 case OUTPUT_FORMAT_ARGUMENT:
753 /* Whether to use the source form of moves or
754 * rewrite them into another format.
757 OutputFormat format = which_output_format(associated_value);
759 /* Rewrite the game in a format suitable for input to
760 * a UCI-compatible engine.
761 * This is actually LALG but involves adjusting a lot of
762 * the other statuses, too.
764 GlobalState.keep_NAGs = FALSE;
765 GlobalState.keep_comments = FALSE;
766 GlobalState.keep_move_numbers = FALSE;
767 GlobalState.keep_checks = FALSE;
768 GlobalState.keep_variations = FALSE;
769 set_output_line_length(5000);
772 GlobalState.output_format = format;
775 case SEVEN_TAG_ROSTER_ARGUMENT:
776 if(GlobalState.tag_output_format == ALL_TAGS ||
777 GlobalState.tag_output_format == SEVEN_TAG_ROSTER) {
778 GlobalState.tag_output_format = SEVEN_TAG_ROSTER;
781 fprintf(GlobalState.logfile,
782 "-%c clashes with another argument.\n",
783 SEVEN_TAG_ROSTER_ARGUMENT);
787 case DONT_KEEP_COMMENTS_ARGUMENT:
788 GlobalState.keep_comments = FALSE;
790 case DONT_KEEP_DUPLICATES_ARGUMENT:
791 /* Make sure that this doesn't clash with -d. */
792 if(GlobalState.duplicate_file == NULL){
793 GlobalState.suppress_duplicates = TRUE;
796 fprintf(GlobalState.logfile,
797 "-%c clashes with -%c flag.\n",
798 DONT_KEEP_DUPLICATES_ARGUMENT,
799 DUPLICATES_FILE_ARGUMENT);
803 case DONT_MATCH_PERMUTATIONS_ARGUMENT:
804 GlobalState.match_permutations = FALSE;
806 case DONT_KEEP_NAGS_ARGUMENT:
807 GlobalState.keep_NAGs = FALSE;
809 case OUTPUT_FEN_STRING_ARGUMENT:
810 /* Output a FEN string of the final position.
811 * This is displayed in a comment.
813 if(GlobalState.add_FEN_comments) {
814 /* Already implied. */
815 GlobalState.output_FEN_string = FALSE;
818 GlobalState.output_FEN_string = TRUE;
821 case CHECK_ONLY_ARGUMENT:
822 /* Report errors, but don't convert. */
823 GlobalState.check_only = TRUE;
825 case KEEP_SILENT_ARGUMENT:
826 /* Turn off progress reporting. */
827 GlobalState.verbose = FALSE;
829 case USE_SOUNDEX_ARGUMENT:
830 /* Use soundex matches for player tags. */
831 GlobalState.use_soundex = TRUE;
833 case MATCH_CHECKMATE_ARGUMENT:
834 /* Match only games that end in checkmate. */
835 GlobalState.match_only_checkmate = TRUE;
837 case SUPPRESS_ORIGINALS_ARGUMENT:
838 GlobalState.suppress_originals = TRUE;
840 case DONT_KEEP_VARIATIONS_ARGUMENT:
841 GlobalState.keep_variations = FALSE;
843 case USE_VIRTUAL_HASH_TABLE_ARGUMENT:
844 GlobalState.use_virtual_hash_table = TRUE;
848 if(*filename != '\0'){
849 read_tag_file(filename);
852 case TAG_ROSTER_ARGUMENT:
853 if(*filename != '\0'){
854 read_tag_roster_file(filename);
858 if(*filename != '\0'){
859 /* Where the list of variations of interest are kept. */
860 FILE *variation_file = must_open_file(filename,"r");
861 /* We wish to search for particular variations. */
862 add_textual_variations_from_file(variation_file);
863 fclose(variation_file);
866 case POSITIONS_ARGUMENT:
867 if(*filename != '\0'){
868 FILE *variation_file = must_open_file(filename,"r");
869 /* We wish to search for positional variations. */
870 add_positional_variations_from_file(variation_file);
871 fclose(variation_file);
874 case ENDINGS_ARGUMENT:
875 if(*filename != '\0'){
876 if(!build_endings(filename)){
882 fprintf(GlobalState.logfile,
883 "Unrecognized argument -%c\n", arg_letter);
887 /* The argument has been expressed in a long-form, i.e. prefixed
889 * Decode and act on the argument.
890 * The associated_value will only be required by some arguments.
891 * Return whether one or both were required.
894 process_long_form_argument(const char *argument, const char *associated_value)
896 if(stringcompare(argument, "addhashcode") == 0) {
897 GlobalState.add_hashcode_tag = TRUE;
900 else if(stringcompare(argument, "append") == 0) {
901 process_argument(APPEND_TO_OUTPUT_FILE_ARGUMENT, associated_value);
904 else if(stringcompare(argument, "checkfile") == 0) {
905 process_argument(CHECK_FILE_ARGUMENT, associated_value);
908 else if(stringcompare(argument, "checkmate") == 0) {
909 process_argument(MATCH_CHECKMATE_ARGUMENT, "");
912 else if(stringcompare(argument, "dumpeco") == 0) {
913 GlobalState.dump_eco = TRUE;
916 else if(stringcompare(argument, "duplicates") == 0) {
917 process_argument(DUPLICATES_FILE_ARGUMENT, associated_value);
920 else if(stringcompare(argument, "evaluation") == 0) {
921 /* Output an evaluation is required with each move. */
922 GlobalState.output_evaluation = TRUE;
925 else if(stringcompare(argument, "fencomments") == 0) {
926 /* Output an evaluation is required with each move. */
927 GlobalState.add_FEN_comments = TRUE;
928 /* Turn off any separate setting of output_FEN_comment. */
929 GlobalState.output_FEN_string = FALSE;
932 else if(stringcompare(argument, "fuzzydepth") == 0) {
933 /* Extract the depth. */
936 if(sscanf(associated_value, "%d",&depth) == 1){
938 GlobalState.fuzzy_match_duplicates = TRUE;
939 GlobalState.fuzzy_match_depth = depth;
942 fprintf(GlobalState.logfile,
943 "--%s requires a number greater than or equal to zero.\n", argument);
948 fprintf(GlobalState.logfile,
949 "--%s requires a number following it.\n", argument);
954 else if(stringcompare(argument, "help") == 0) {
955 process_argument(HELP_ARGUMENT, "");
958 else if(stringcompare(argument, "keepbroken") == 0) {
959 GlobalState.keep_broken_games = TRUE;
962 else if(stringcompare(argument, "linelength") == 0) {
963 process_argument(LINE_WIDTH_ARGUMENT,
967 else if(stringcompare(argument, "markmatches") == 0) {
968 if(*associated_value != '\0') {
969 GlobalState.add_position_match_comments = TRUE;
970 GlobalState.position_match_comment = copy_string(associated_value);
973 fprintf(GlobalState.logfile,
974 "--markmatches requires a comment string following it.\n");
979 else if(stringcompare(argument, "nochecks") == 0) {
980 GlobalState.keep_checks = FALSE;
983 else if(stringcompare(argument, "nocomments") == 0) {
984 process_argument(DONT_KEEP_COMMENTS_ARGUMENT, "");
987 else if(stringcompare(argument, "noduplicates") == 0) {
988 process_argument(DONT_KEEP_DUPLICATES_ARGUMENT, "");
991 else if(stringcompare(argument, "nomovenumbers") == 0) {
992 GlobalState.keep_move_numbers = FALSE;
995 else if(stringcompare(argument, "nonags") == 0) {
996 process_argument(DONT_KEEP_NAGS_ARGUMENT, "");
999 else if(stringcompare(argument, "noresults") == 0) {
1000 GlobalState.keep_results = FALSE;
1003 else if(stringcompare(argument, "notags") == 0) {
1004 if(GlobalState.tag_output_format == ALL_TAGS ||
1005 GlobalState.tag_output_format == NO_TAGS) {
1006 GlobalState.tag_output_format = NO_TAGS;
1009 fprintf(GlobalState.logfile,
1010 "--notags clashes with another argument.\n");
1015 else if(stringcompare(argument, "nounique") == 0) {
1016 process_argument(SUPPRESS_ORIGINALS_ARGUMENT, "");
1019 else if(stringcompare(argument, "novars") == 0) {
1020 process_argument(DONT_KEEP_VARIATIONS_ARGUMENT, "");
1023 else if(stringcompare(argument, "selectonly") == 0) {
1024 unsigned long selection = 0;
1026 /* Extract the selected match number. */
1027 if(sscanf(associated_value, "%lu",&selection) == 1){
1029 GlobalState.matching_game_number = selection;
1032 fprintf(GlobalState.logfile,
1033 "--%s requires a number greater than zero.\n", argument);
1038 fprintf(GlobalState.logfile,
1039 "--%s requires a number greater than zero following it.\n", argument);
1044 else if(stringcompare(argument, "output") == 0) {
1045 process_argument(WRITE_TO_OUTPUT_FILE_ARGUMENT, associated_value);
1048 else if(stringcompare(argument, "plylimit") == 0) {
1051 /* Extract the limit. */
1052 if(sscanf(associated_value, "%d",&limit) == 1){
1054 GlobalState.output_ply_limit = limit;
1057 fprintf(GlobalState.logfile,
1058 "--%s requires a number greater than or equal to zero.\n", argument);
1063 fprintf(GlobalState.logfile,
1064 "--%s requires a number following it.\n", argument);
1069 else if(stringcompare(argument, "seven") == 0) {
1070 process_argument(SEVEN_TAG_ROSTER_ARGUMENT, "");
1073 else if(stringcompare(argument, "stalemate") == 0) {
1074 GlobalState.match_only_stalemate = TRUE;
1077 else if(stringcompare(argument, "totalplycount") == 0) {
1078 GlobalState.output_total_plycount = TRUE;
1081 else if(stringcompare(argument, "startpos") == 0) {
1082 GlobalState.start_position = atol(associated_value);
1085 else if(stringcompare(argument, "endpos") == 0) {
1086 GlobalState.end_position = atol(associated_value);
1089 else if(stringcompare(argument, "startfilenum") == 0) {
1090 GlobalState.start_file_number = atoi(associated_value);
1093 else if(stringcompare(argument, "version") == 0) {
1094 fprintf(GlobalState.logfile, "pgn-extract %s\n", CURRENT_VERSION);
1099 fprintf(GlobalState.logfile,
1100 "Unrecognised long-form argument: --%s\n",