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 "--duplicates - see -d",
180 "--evaluation - include a position evaluation after each move",
181 "--fencomments - include a FEN string after each move",
182 "--fuzzydepth plies - positional duplicates match",
184 "--keepbroken - retain games with errors",
185 "--linelength - see -w",
186 "--markmatches - mark positional and material matches with a comment; see -t, -v, and -z",
187 "--nochecks - don't output + and # after moves.",
188 "--nocomments - see -C",
189 "--noduplicates - see -D",
190 "--nomovenumbers - don't output move numbers.",
192 "--noresults - don't output results.",
193 "--notags - don't output any tags.",
194 "--nounique - see -U",
197 "--plylimit - limit the number of plies output.",
198 "--selectonly N - only output the Nth matched game (N > 0)",
200 "--stalemate - only output games that end in stalemate.",
201 "--totalplycount - include a tag with the total number of plies in a game.",
202 "--version - print the current version number and exit.",
204 /* Must be NULL terminated. */
208 const char **data = help_data;
210 fprintf(GlobalState.logfile,
211 "pgn-extract %s (%s): a Portable Game Notation (PGN) manipulator.\n",
212 CURRENT_VERSION,__DATE__);
213 fprintf(GlobalState.logfile,
214 "Copyright (C) 1994-2014 David J. Barnes (d.j.barnes@kent.ac.uk)\n");
215 fprintf(GlobalState.logfile,"%s\n\n",URL);
216 fprintf(GlobalState.logfile,"Usage: pgn-extract [arguments] [file.pgn ...]\n");
218 for(; *data != NULL; data++){
219 fprintf(GlobalState.logfile,"%s\n",*data);
225 read_args_file(const char *infile)
227 FILE *fp = fopen(infile,"r");
230 fprintf(GlobalState.logfile,"Cannot open %s for reading.\n",infile);
234 ArgType linetype = NO_ARGUMENT_MATCH;
236 while((line = read_line(fp)) != NULL){
237 if(blank_line(line)){
241 nexttype = classify_arg(line);
242 if(nexttype == NO_ARGUMENT_MATCH){
243 if(*line == argument_prefix[0]){
244 /* Treat the line as a source file name. */
245 add_filename_to_source_list(&line[1],NORMALFILE);
247 else if(linetype != NO_ARGUMENT_MATCH){
248 /* Handle the line. */
251 add_textual_variation_from_line(line);
253 case POSITIONS_ARGUMENT:
254 add_positional_variation_from_line(line);
257 process_tag_line(infile,line);
259 case TAG_ROSTER_ARGUMENT:
260 process_roster_line(line);
262 case ENDINGS_ARGUMENT:
263 process_ending_line(line);
267 fprintf(GlobalState.logfile,
268 "Internal error: unknown linetype %d in read_args_file\n",
275 /* It should have been a line applying to the
278 fprintf(GlobalState.logfile,
279 "Missing argument type for line %s in the argument file.\n",
286 /* Arguments with a possible additional
288 * All of these apply only to the current
289 * line in the argument file.
291 case WRITE_TO_OUTPUT_FILE_ARGUMENT:
292 case APPEND_TO_OUTPUT_FILE_ARGUMENT:
293 case WRITE_TO_LOG_FILE_ARGUMENT:
294 case APPEND_TO_LOG_FILE_ARGUMENT:
295 case DUPLICATES_FILE_ARGUMENT:
296 case USE_ECO_FILE_ARGUMENT:
297 case CHECK_FILE_ARGUMENT:
298 case FILE_OF_FILES_ARGUMENT:
299 case BOUNDS_ARGUMENT:
300 case GAMES_PER_FILE_ARGUMENT:
301 case ECO_OUTPUT_LEVEL_ARGUMENT:
302 case FILE_OF_ARGUMENTS_ARGUMENT:
303 case NON_MATCHING_GAMES_ARGUMENT:
304 case TAG_EXTRACTION_ARGUMENT:
305 case LINE_WIDTH_ARGUMENT:
306 case OUTPUT_FORMAT_ARGUMENT:
307 process_argument(line[argument_prefix_len],
308 &line[argument_prefix_len+1]);
309 linetype = NO_ARGUMENT_MATCH;
311 case LONG_FORM_ARGUMENT:
313 char *arg = &line[argument_prefix_len+1];
314 char *space = strchr(arg, ' ');
316 /* We need to drop an associated value from arg. */
317 int arglen = space - arg;
318 char *just_arg = (char *) MallocOrDie(arglen + 1);
319 strncpy(just_arg, arg, arglen);
320 just_arg[arglen] = '\0';
321 process_long_form_argument(just_arg,
322 skip_leading_spaces(space));
325 process_long_form_argument(arg, "");
326 linetype = NO_ARGUMENT_MATCH;
331 /* Arguments with no additional
333 * All of these apply only to the current
334 * line in the argument file.
336 case SEVEN_TAG_ROSTER_ARGUMENT:
338 case ALTERNATIVE_HELP_ARGUMENT:
339 case DONT_KEEP_COMMENTS_ARGUMENT:
340 case DONT_KEEP_DUPLICATES_ARGUMENT:
341 case DONT_MATCH_PERMUTATIONS_ARGUMENT:
342 case DONT_KEEP_NAGS_ARGUMENT:
343 case OUTPUT_FEN_STRING_ARGUMENT:
344 case CHECK_ONLY_ARGUMENT:
345 case KEEP_SILENT_ARGUMENT:
346 case USE_SOUNDEX_ARGUMENT:
347 case MATCH_CHECKMATE_ARGUMENT:
348 case SUPPRESS_ORIGINALS_ARGUMENT:
349 case DONT_KEEP_VARIATIONS_ARGUMENT:
350 case USE_VIRTUAL_HASH_TABLE_ARGUMENT:
351 process_argument(line[argument_prefix_len],"");
352 linetype = NO_ARGUMENT_MATCH;
355 /* Arguments whose values persist beyond
359 case POSITIONS_ARGUMENT:
360 case ENDINGS_ARGUMENT:
362 case TAG_ROSTER_ARGUMENT:
363 process_argument(line[argument_prefix_len],
364 &line[argument_prefix_len+1]);
365 /* Apply this type to subsequent lines. */
379 /* Determine which (if any) type of argument is
380 * indicated by the contents of the current line.
381 * Arguments are assumed to be surrounded by
385 classify_arg(const char *line)
387 /* Valid arguments must have at least one character beyond
390 static const size_t min_argument_length = 1+sizeof(argument_prefix)-1;
391 size_t line_length = strlen(line);
393 /* Check for a line of the form:
396 if((strncmp(line,argument_prefix,argument_prefix_len) == 0) &&
397 (line_length >= min_argument_length)){
398 char argument_letter = line[argument_prefix_len];
399 switch(argument_letter){
402 case POSITIONS_ARGUMENT:
403 case ENDINGS_ARGUMENT:
404 case TAG_EXTRACTION_ARGUMENT:
405 case LINE_WIDTH_ARGUMENT:
406 case OUTPUT_FORMAT_ARGUMENT:
407 case SEVEN_TAG_ROSTER_ARGUMENT:
408 case FILE_OF_ARGUMENTS_ARGUMENT:
409 case NON_MATCHING_GAMES_ARGUMENT:
410 case DONT_KEEP_COMMENTS_ARGUMENT:
411 case DONT_KEEP_DUPLICATES_ARGUMENT:
412 case DONT_KEEP_NAGS_ARGUMENT:
413 case DONT_MATCH_PERMUTATIONS_ARGUMENT:
414 case OUTPUT_FEN_STRING_ARGUMENT:
415 case CHECK_ONLY_ARGUMENT:
416 case KEEP_SILENT_ARGUMENT:
417 case USE_SOUNDEX_ARGUMENT:
418 case MATCH_CHECKMATE_ARGUMENT:
419 case SUPPRESS_ORIGINALS_ARGUMENT:
420 case DONT_KEEP_VARIATIONS_ARGUMENT:
421 case WRITE_TO_OUTPUT_FILE_ARGUMENT:
422 case WRITE_TO_LOG_FILE_ARGUMENT:
423 case APPEND_TO_LOG_FILE_ARGUMENT:
424 case APPEND_TO_OUTPUT_FILE_ARGUMENT:
425 case DUPLICATES_FILE_ARGUMENT:
426 case USE_ECO_FILE_ARGUMENT:
427 case CHECK_FILE_ARGUMENT:
428 case FILE_OF_FILES_ARGUMENT:
429 case BOUNDS_ARGUMENT:
430 case GAMES_PER_FILE_ARGUMENT:
431 case ECO_OUTPUT_LEVEL_ARGUMENT:
433 case ALTERNATIVE_HELP_ARGUMENT:
434 case TAG_ROSTER_ARGUMENT:
435 case LONG_FORM_ARGUMENT:
436 return (ArgType) argument_letter;
438 fprintf(GlobalState.logfile,
439 "Unrecognized argument: %s in the argument file.\n",
442 return NO_ARGUMENT_MATCH;
446 return NO_ARGUMENT_MATCH;
450 /* Process the argument character and its associated value.
451 * This function processes arguments from the command line and
452 * from an argument file associated with the -A argument.
454 * An argument -ofile.pgn would be passed in as:
455 * 'o' and "file.pgn".
456 * A zero-length string for associated_value is not necessarily
457 * an error, e.g. -e has an optional following filename.
458 * NB: If the associated_value is to be used beyond this function,
462 process_argument(char arg_letter,const char *associated_value)
463 { /* Provide an alias for associated_value because it will
464 * often represent a file name.
466 const char *filename = skip_leading_spaces(associated_value);
469 case WRITE_TO_OUTPUT_FILE_ARGUMENT:
470 case APPEND_TO_OUTPUT_FILE_ARGUMENT:
471 if(GlobalState.ECO_level > 0){
472 fprintf(GlobalState.logfile,"-%c conflicts with -E\n",
475 else if(GlobalState.games_per_file > 0){
476 fprintf(GlobalState.logfile,"-%c conflicts with -#\n",
479 else if(GlobalState.output_filename != NULL){
480 fprintf(GlobalState.logfile,
481 "-%c: File %s has already been selected for output.\n",
482 arg_letter,GlobalState.output_filename);
485 else if(*filename == '\0'){
486 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",arg_letter);
490 if(GlobalState.outputfile != NULL){
491 (void) fclose(GlobalState.outputfile);
493 if(arg_letter == WRITE_TO_OUTPUT_FILE_ARGUMENT){
494 GlobalState.outputfile = must_open_file(filename,"w");
497 GlobalState.outputfile = must_open_file(filename,"a");
499 GlobalState.output_filename = filename;
502 case WRITE_TO_LOG_FILE_ARGUMENT:
503 case APPEND_TO_LOG_FILE_ARGUMENT:
504 /* Take precautions against multiple log files. */
505 if((GlobalState.logfile != stderr) && (GlobalState.logfile != NULL)){
506 (void) fclose(GlobalState.logfile);
508 if(arg_letter == WRITE_TO_LOG_FILE_ARGUMENT){
509 GlobalState.logfile = fopen(filename,"w");
512 GlobalState.logfile = fopen(filename,"a");
514 if(GlobalState.logfile == NULL){
515 fprintf(stderr,"Unable to open %s for writing.\n",filename);
516 GlobalState.logfile = stderr;
519 case DUPLICATES_FILE_ARGUMENT:
520 if(*filename == '\0'){
521 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",arg_letter);
524 else if(GlobalState.suppress_duplicates){
525 fprintf(GlobalState.logfile,
526 "-%c clashes with the -%c flag.\n",arg_letter,
527 DONT_KEEP_DUPLICATES_ARGUMENT);
531 GlobalState.duplicate_file = must_open_file(filename,"w");
534 case USE_ECO_FILE_ARGUMENT:
535 GlobalState.add_ECO = TRUE;
536 if(*filename != '\0'){
537 GlobalState.eco_file = copy_string(filename);
539 else if((filename = getenv("ECO_FILE")) != NULL){
540 GlobalState.eco_file = filename;
543 /* Use the default which is already set up. */
547 case ECO_OUTPUT_LEVEL_ARGUMENT:
550 if(GlobalState.output_filename != NULL){
551 fprintf(GlobalState.logfile,
552 "-%c: File %s has already been selected for output.\n",
554 GlobalState.output_filename);
557 else if(GlobalState.games_per_file > 0){
558 fprintf(GlobalState.logfile,
559 "-%c conflicts with -#.\n",
563 else if(sscanf(associated_value,"%u",&level) != 1){
564 fprintf(GlobalState.logfile,
565 "-%c requires a number attached, e.g., -%c1.\n",
566 arg_letter,arg_letter);
569 else if((level < MIN_ECO_LEVEL) || (level > MAX_ECO_LEVEL)){
570 fprintf(GlobalState.logfile,
571 "-%c level should be between %u and %u.\n",
572 MIN_ECO_LEVEL,MAX_ECO_LEVEL,arg_letter);
576 GlobalState.ECO_level = level;
580 case CHECK_FILE_ARGUMENT:
581 if(*filename != '\0'){
582 /* See if it is a single PGN file, or a list
585 size_t len = strlen(filename);
586 /* Check for a .PGN suffix. */
587 const char *suffix = output_file_suffix(SAN);
589 if((len > strlen(suffix)) &&
590 (stringcompare(&filename[len-strlen(suffix)],
592 add_filename_to_source_list(filename,CHECKFILE);
595 FILE *fp = must_open_file(filename,"r");
596 add_filename_list_from_file(fp,CHECKFILE);
601 case FILE_OF_FILES_ARGUMENT:
602 if(*filename != '\0'){
603 FILE *fp = must_open_file(filename,"r");
604 add_filename_list_from_file(fp,NORMALFILE);
608 fprintf(GlobalState.logfile,"Filename expected with -%c\n",
612 case BOUNDS_ARGUMENT:
614 /* Bounds on the number of moves are to be found.
615 * "l#" means less-than-or-equal-to.
616 * "g#" means greater-than-or-equal-to.
617 * Otherwise "#" (or "e#") means that number.
619 /* Equal by default. */
623 const char *bound = associated_value;
633 if(!isdigit((int) *bound)){
634 fprintf(GlobalState.logfile,
635 "-%c must be followed by e, l, or u.\n",
641 if(Ok && (sscanf(bound,"%u",&value) == 1)){
642 GlobalState.check_move_bounds = TRUE;
645 GlobalState.lower_move_bound = value;
646 GlobalState.upper_move_bound = value;
649 if(value <= GlobalState.upper_move_bound){
650 GlobalState.lower_move_bound = value;
653 fprintf(GlobalState.logfile,
654 "Lower bound is greater than the upper bound; -%c ignored.\n",
660 if(value >= GlobalState.lower_move_bound){
661 GlobalState.upper_move_bound = value;
664 fprintf(GlobalState.logfile,
665 "Upper bound is smaller than the lower bound; -%c ignored.\n",
673 fprintf(GlobalState.logfile,
674 "-%c should be in the form -%c[elu]number.\n",
675 arg_letter,arg_letter);
683 case GAMES_PER_FILE_ARGUMENT:
684 if(GlobalState.ECO_level > 0){
685 fprintf(GlobalState.logfile,
686 "-%c conflicts with -E.\n",arg_letter);
689 else if(GlobalState.output_filename != NULL){
690 fprintf(GlobalState.logfile,
691 "-%c: File %s has already been selected for output.\n",
693 GlobalState.output_filename);
696 else if(sscanf(associated_value,"%u",
697 &GlobalState.games_per_file) != 1){
698 fprintf(GlobalState.logfile,
699 "-%c should be followed by an unsigned integer.\n",
707 case FILE_OF_ARGUMENTS_ARGUMENT:
708 if(*filename != '\0'){
709 /* @@@ Potentially recursive call. Is this safe? */
710 read_args_file(filename);
713 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",
717 case NON_MATCHING_GAMES_ARGUMENT:
718 if(*filename != '\0'){
719 if(GlobalState.non_matching_file != NULL){
720 (void) fclose(GlobalState.non_matching_file);
722 GlobalState.non_matching_file = must_open_file(filename,"w");
725 fprintf(GlobalState.logfile,"Usage: -%cfilename.\n",arg_letter);
729 case TAG_EXTRACTION_ARGUMENT:
730 /* A single tag extraction criterion. */
731 extract_tag_argument(associated_value);
733 case LINE_WIDTH_ARGUMENT:
734 { /* Specify an output line width. */
737 if(sscanf(associated_value,"%u",&length) > 0){
738 set_output_line_length(length);
741 fprintf(GlobalState.logfile,
742 "-%c should be followed by an unsigned integer.\n",
751 case OUTPUT_FORMAT_ARGUMENT:
752 /* Whether to use the source form of moves or
753 * rewrite them into another format.
756 OutputFormat format = which_output_format(associated_value);
758 /* Rewrite the game in a format suitable for input to
759 * a UCI-compatible engine.
760 * This is actually LALG but involves adjusting a lot of
761 * the other statuses, too.
763 GlobalState.keep_NAGs = FALSE;
764 GlobalState.keep_comments = FALSE;
765 GlobalState.keep_move_numbers = FALSE;
766 GlobalState.keep_checks = FALSE;
767 GlobalState.keep_variations = FALSE;
768 set_output_line_length(5000);
771 GlobalState.output_format = format;
774 case SEVEN_TAG_ROSTER_ARGUMENT:
775 if(GlobalState.tag_output_format == ALL_TAGS ||
776 GlobalState.tag_output_format == SEVEN_TAG_ROSTER) {
777 GlobalState.tag_output_format = SEVEN_TAG_ROSTER;
780 fprintf(GlobalState.logfile,
781 "-%c clashes with another argument.\n",
782 SEVEN_TAG_ROSTER_ARGUMENT);
786 case DONT_KEEP_COMMENTS_ARGUMENT:
787 GlobalState.keep_comments = FALSE;
789 case DONT_KEEP_DUPLICATES_ARGUMENT:
790 /* Make sure that this doesn't clash with -d. */
791 if(GlobalState.duplicate_file == NULL){
792 GlobalState.suppress_duplicates = TRUE;
795 fprintf(GlobalState.logfile,
796 "-%c clashes with -%c flag.\n",
797 DONT_KEEP_DUPLICATES_ARGUMENT,
798 DUPLICATES_FILE_ARGUMENT);
802 case DONT_MATCH_PERMUTATIONS_ARGUMENT:
803 GlobalState.match_permutations = FALSE;
805 case DONT_KEEP_NAGS_ARGUMENT:
806 GlobalState.keep_NAGs = FALSE;
808 case OUTPUT_FEN_STRING_ARGUMENT:
809 /* Output a FEN string of the final position.
810 * This is displayed in a comment.
812 if(GlobalState.add_FEN_comments) {
813 /* Already implied. */
814 GlobalState.output_FEN_string = FALSE;
817 GlobalState.output_FEN_string = TRUE;
820 case CHECK_ONLY_ARGUMENT:
821 /* Report errors, but don't convert. */
822 GlobalState.check_only = TRUE;
824 case KEEP_SILENT_ARGUMENT:
825 /* Turn off progress reporting. */
826 GlobalState.verbose = FALSE;
828 case USE_SOUNDEX_ARGUMENT:
829 /* Use soundex matches for player tags. */
830 GlobalState.use_soundex = TRUE;
832 case MATCH_CHECKMATE_ARGUMENT:
833 /* Match only games that end in checkmate. */
834 GlobalState.match_only_checkmate = TRUE;
836 case SUPPRESS_ORIGINALS_ARGUMENT:
837 GlobalState.suppress_originals = TRUE;
839 case DONT_KEEP_VARIATIONS_ARGUMENT:
840 GlobalState.keep_variations = FALSE;
842 case USE_VIRTUAL_HASH_TABLE_ARGUMENT:
843 GlobalState.use_virtual_hash_table = TRUE;
847 if(*filename != '\0'){
848 read_tag_file(filename);
851 case TAG_ROSTER_ARGUMENT:
852 if(*filename != '\0'){
853 read_tag_roster_file(filename);
857 if(*filename != '\0'){
858 /* Where the list of variations of interest are kept. */
859 FILE *variation_file = must_open_file(filename,"r");
860 /* We wish to search for particular variations. */
861 add_textual_variations_from_file(variation_file);
862 fclose(variation_file);
865 case POSITIONS_ARGUMENT:
866 if(*filename != '\0'){
867 FILE *variation_file = must_open_file(filename,"r");
868 /* We wish to search for positional variations. */
869 add_positional_variations_from_file(variation_file);
870 fclose(variation_file);
873 case ENDINGS_ARGUMENT:
874 if(*filename != '\0'){
875 if(!build_endings(filename)){
881 fprintf(GlobalState.logfile,
882 "Unrecognized argument -%c\n", arg_letter);
886 /* The argument has been expressed in a long-form, i.e. prefixed
888 * Decode and act on the argument.
889 * The associated_value will only be required by some arguments.
890 * Return whether one or both were required.
893 process_long_form_argument(const char *argument, const char *associated_value)
895 if(stringcompare(argument, "addhashcode") == 0) {
896 GlobalState.add_hashcode_tag = TRUE;
899 else if(stringcompare(argument, "append") == 0) {
900 process_argument(APPEND_TO_OUTPUT_FILE_ARGUMENT, associated_value);
903 else if(stringcompare(argument, "checkfile") == 0) {
904 process_argument(CHECK_FILE_ARGUMENT, associated_value);
907 else if(stringcompare(argument, "checkmate") == 0) {
908 process_argument(MATCH_CHECKMATE_ARGUMENT, "");
911 else if(stringcompare(argument, "duplicates") == 0) {
912 process_argument(DUPLICATES_FILE_ARGUMENT, associated_value);
915 else if(stringcompare(argument, "evaluation") == 0) {
916 /* Output an evaluation is required with each move. */
917 GlobalState.output_evaluation = TRUE;
920 else if(stringcompare(argument, "fencomments") == 0) {
921 /* Output an evaluation is required with each move. */
922 GlobalState.add_FEN_comments = TRUE;
923 /* Turn off any separate setting of output_FEN_comment. */
924 GlobalState.output_FEN_string = FALSE;
927 else if(stringcompare(argument, "fuzzydepth") == 0) {
928 /* Extract the depth. */
931 if(sscanf(associated_value, "%d",&depth) == 1){
933 GlobalState.fuzzy_match_duplicates = TRUE;
934 GlobalState.fuzzy_match_depth = depth;
937 fprintf(GlobalState.logfile,
938 "--%s requires a number greater than or equal to zero.\n", argument);
943 fprintf(GlobalState.logfile,
944 "--%s requires a number following it.\n", argument);
949 else if(stringcompare(argument, "help") == 0) {
950 process_argument(HELP_ARGUMENT, "");
953 else if(stringcompare(argument, "keepbroken") == 0) {
954 GlobalState.keep_broken_games = TRUE;
957 else if(stringcompare(argument, "linelength") == 0) {
958 process_argument(LINE_WIDTH_ARGUMENT,
962 else if(stringcompare(argument, "markmatches") == 0) {
963 if(*associated_value != '\0') {
964 GlobalState.add_position_match_comments = TRUE;
965 GlobalState.position_match_comment = copy_string(associated_value);
968 fprintf(GlobalState.logfile,
969 "--markmatches requires a comment string following it.\n");
974 else if(stringcompare(argument, "nochecks") == 0) {
975 GlobalState.keep_checks = FALSE;
978 else if(stringcompare(argument, "nocomments") == 0) {
979 process_argument(DONT_KEEP_COMMENTS_ARGUMENT, "");
982 else if(stringcompare(argument, "noduplicates") == 0) {
983 process_argument(DONT_KEEP_DUPLICATES_ARGUMENT, "");
986 else if(stringcompare(argument, "nomovenumbers") == 0) {
987 GlobalState.keep_move_numbers = FALSE;
990 else if(stringcompare(argument, "nonags") == 0) {
991 process_argument(DONT_KEEP_NAGS_ARGUMENT, "");
994 else if(stringcompare(argument, "noresults") == 0) {
995 GlobalState.keep_results = FALSE;
998 else if(stringcompare(argument, "notags") == 0) {
999 if(GlobalState.tag_output_format == ALL_TAGS ||
1000 GlobalState.tag_output_format == NO_TAGS) {
1001 GlobalState.tag_output_format = NO_TAGS;
1004 fprintf(GlobalState.logfile,
1005 "--notags clashes with another argument.\n");
1010 else if(stringcompare(argument, "nounique") == 0) {
1011 process_argument(SUPPRESS_ORIGINALS_ARGUMENT, "");
1014 else if(stringcompare(argument, "novars") == 0) {
1015 process_argument(DONT_KEEP_VARIATIONS_ARGUMENT, "");
1018 else if(stringcompare(argument, "selectonly") == 0) {
1019 unsigned long selection = 0;
1021 /* Extract the selected match number. */
1022 if(sscanf(associated_value, "%lu",&selection) == 1){
1024 GlobalState.matching_game_number = selection;
1027 fprintf(GlobalState.logfile,
1028 "--%s requires a number greater than zero.\n", argument);
1033 fprintf(GlobalState.logfile,
1034 "--%s requires a number greater than zero following it.\n", argument);
1039 else if(stringcompare(argument, "output") == 0) {
1040 process_argument(WRITE_TO_OUTPUT_FILE_ARGUMENT, associated_value);
1043 else if(stringcompare(argument, "plylimit") == 0) {
1046 /* Extract the limit. */
1047 if(sscanf(associated_value, "%d",&limit) == 1){
1049 GlobalState.output_ply_limit = limit;
1052 fprintf(GlobalState.logfile,
1053 "--%s requires a number greater than or equal to zero.\n", argument);
1058 fprintf(GlobalState.logfile,
1059 "--%s requires a number following it.\n", argument);
1064 else if(stringcompare(argument, "seven") == 0) {
1065 process_argument(SEVEN_TAG_ROSTER_ARGUMENT, "");
1068 else if(stringcompare(argument, "stalemate") == 0) {
1069 GlobalState.match_only_stalemate = TRUE;
1072 else if(stringcompare(argument, "totalplycount") == 0) {
1073 GlobalState.output_total_plycount = TRUE;
1076 else if(stringcompare(argument, "version") == 0) {
1077 fprintf(GlobalState.logfile, "pgn-extract %s\n", CURRENT_VERSION);
1082 fprintf(GlobalState.logfile,
1083 "Unrecognised long-form argument: --%s\n",