/*
* Gregorio is a program that translates gabc files to GregorioTeX
* This file implements the command line interface of Gregorio.
*
* Copyright (C) 2006-2018 The Gregorio Project (see CONTRIBUTORS.md)
*
* This file is part of Gregorio.
*
* Gregorio is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Gregorio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Gregorio. If not, see .
*/
static const char *copyright =
"Copyright (C) 2006-2018 Gregorio Project authors (see CONTRIBUTORS.md)";
#include "config.h"
#include
#include
#include
#ifdef USE_KPSE
#include
#else
#include
#endif
#include /* for strcmp */
#include
#include
#include
#include
#include "struct.h"
#include "plugins.h"
#include "messages.h"
#include "characters.h"
#include "support.h"
#include "gabc/gabc.h"
#include "vowel/vowel.h"
#ifndef MODULE_PATH_ENV
#define MODULE_PATH_ENV "MODULE_PATH"
#endif
typedef enum gregorio_file_format {
FORMAT_UNSET = 0,
GABC,
GTEX,
DUMP
} gregorio_file_format;
#define GABC_STR "gabc"
#define GTEX_STR "gtex"
#define DUMP_STR "dump"
#define DEFAULT_INPUT_FORMAT GABC
#define DEFAULT_OUTPUT_FORMAT GTEX
/* realpath is not in mingw32 */
#ifdef _WIN32
/* _MAX_PATH is being passed for the maxLength (third) argument of _fullpath,
* but we are always passing NULL for the absPath (first) argument, so it will
* be ignored per the MSDN documentation */
#define gregorio_realpath(path,resolved_path) _fullpath(resolved_path,path,_MAX_PATH)
#else
#ifdef FUNC_REALPATH_WORKS
#define gregorio_realpath(path,resolved_path) realpath(path,resolved_path)
#else
/* When realpath doesn't work (on an older system), we are forced to use
* PATH_MAX to allocate a buffer */
#define gregorio_realpath(path,resolved_path) realpath(path,gregorio_malloc(PATH_MAX))
#endif /* FUNC_REALPATH_WORKS */
#endif /* _WIN32 */
/* define_path attempts to canonicalize the pathname of a given string */
static char *define_path(char *current_directory, char *string)
{
int length;
char *file_name;
char *temp_name;
char *base_name;
#ifdef _WIN32
char *last_backslash;
#endif
temp_name = gregorio_strdup(string);
base_name = strrchr(temp_name, '/');
#ifdef _WIN32
last_backslash = strrchr(temp_name, '\\');
if (last_backslash > base_name) {
base_name = last_backslash;
}
#endif
if (base_name) {
/* some path was supplied */
*base_name = '\0';
base_name++;
/* try to resolve it */
file_name = gregorio_realpath(temp_name, NULL);
if (!file_name) {
/* it's not reasonable to cover this failure in testing */
/* LCOV_EXCL_START */
fprintf(stderr, "the directory %s for %s does not exist\n",
temp_name, base_name);
gregorio_exit(1);
/* LCOV_EXCL_STOP */
}
} else {
/* no path was supplied */
base_name = string;
file_name = gregorio_malloc(
strlen(current_directory) + strlen(base_name) + 2);
strcpy(file_name, current_directory);
}
/* build the file name */
length = strlen(file_name);
file_name = gregorio_realloc(file_name, length + strlen(base_name) + 2);
file_name[length] = '/';
strcpy(file_name + length + 1, base_name);
free(temp_name);
return file_name;
}
/* function that returns the filename without the extension */
static char *get_base_filename(char *fbasename)
{
char *p;
int l;
char *ret;
p = strrchr(fbasename, '.');
if (!p || strchr(p, '/') || strchr(p, '\\')) {
return gregorio_strdup(fbasename);
}
l = strlen(fbasename) - strlen(p);
ret = (char *) gregorio_malloc(l + 1);
gregorio_snprintf(ret, l + 1, "%s", fbasename);
ret[l] = '\0';
return ret;
}
/* function that adds the good extension to a basename (without extension) */
static char *get_output_filename(char *fbasename, const char *extension)
{
char *output_filename = NULL;
output_filename =
(char *) gregorio_malloc((strlen(extension) + strlen(fbasename) + 2));
output_filename = strcpy(output_filename, fbasename);
output_filename = strcat(output_filename, ".");
output_filename = strcat(output_filename, extension);
return output_filename;
}
static void print_licence(void)
{
printf("Gregorio: Gregorian chant score engraving.\n\
%s\n\
\n\
This program is free software: you can redistribute it and/or modify\n\
it under the terms of the GNU General Public License as published by\n\
the Free Software Foundation, either version 3 of the License, or\n\
(at your option) any later version.\n\
\n", copyright);
printf("This program is distributed in the hope that it will be useful,\n\
but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
GNU General Public License for more details.\n\
\n\
You should have received a copy of the GNU General Public License\n\
along with this program. If not, see .\n\
");
}
static void print_usage(char *name)
{
printf(_("Usage: %s [OPTION]... [-s | INPUT_FILE]\n\
\nEngrave Gregorian chant scores, convert a gabc file to GregorioTeX.\n\n\
Options:\n\
-o, --output-file FILE write output to FILE,\n\
default is basename(INPUT_FILE).FORMAT\n\
-S, --stdout write output to stdout\n\
-s, --stdin read input from stdin\n\
-l, --messages-file FILE output messages to FILE (default: stderr)\n\
-F, --output-format FORMAT\n\
specify output format (default: gtex)\n"), name);
printf(_(" -f, --input-format FORMAT\n\
specify input format (default: gabc)\n\
-p, --point-and-click generate Lilypond point and click information\n\
-h, --help print this help message\n\
-V, --version print version and exit\n"));
printf(_("\
-L, --license print licence\n\
-v, --verbose verbose mode\n\
-W, --all-warnings output warnings\n\
-D, --deprecation-errors treat deprecation warnings as errors\n\
-d, --debug output debug information\n\
\n\
Formats:\n\
gabc gabc\n\
gtex GregorioTeX\n\
dump plain text dump (for debugging purpose)\n\
\n\
See <" PACKAGE_URL "> for general documentation,\n\
GregorioRef.pdf and GregorioNabcRef.pdf for full documentation.\
\n"));
}
static void print_short_usage(char *name)
{
fprintf(stderr, "Usage: %s [OPTION]... [-s | INPUT_FILE]\n\
Try '%s --help' for more information.\n", name, name);
}
static void check_input_clobber(char *input_file_name, char *output_file_name)
{
if (input_file_name && output_file_name) {
char *absolute_input_file_name;
char *absolute_output_file_name;
char *current_directory;
int file_cmp;
size_t bufsize = 128;
char *buf = gregorio_malloc(bufsize);
while ((current_directory = getcwd(buf, bufsize)) == NULL
&& errno == ERANGE && bufsize < MAX_BUF_GROWTH) {
free(buf);
bufsize <<= 1;
buf = gregorio_malloc(bufsize);
}
if (current_directory == NULL) {
fprintf(stderr, _("can't determine current directory"));
free(buf);
gregorio_exit(1);
}
absolute_input_file_name = define_path(current_directory, input_file_name);
absolute_output_file_name = define_path(current_directory, output_file_name);
file_cmp = strcmp(absolute_input_file_name, absolute_output_file_name);
if (file_cmp == 0) {
fprintf(stderr, "error: refusing to overwrite the input file\n");
}
free(buf);
free(absolute_input_file_name);
free(absolute_output_file_name);
if (file_cmp == 0) {
gregorio_exit(1);
}
}
}
static char *encode_point_and_click_filename(char *input_file_name)
{
/* percent-encoding favors capital hex digits */
static const char *const hex = "0123456789ABCDEF";
char *filename, *result = NULL, *r = NULL, *p;
filename = gregorio_realpath(input_file_name, NULL);
if (!filename) {
/* it's not reasonable to generate the system error that would cause
* this to fail */
/* LCOV_EXCL_START */
fprintf(stderr, "error: unable to resolve %s\n", input_file_name);
gregorio_exit(1);
/* LCOV_EXCL_STOP */
}
/* 2 extra characters for a possible leading slash and final NUL */
r = result = gregorio_malloc(strlen(filename) * 4 + 2);
#ifdef _WIN32
*(r++) = '/';
#endif
for (p = filename; *p; ++p) {
#ifdef _WIN32
if (*p == '\\') {
*p = '/';
}
#endif
/* note that -, _ and ~ are conspicuously missing from this list
* because they cause trouble in TeX; we will percent-encode them */
if ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p < 'z')
|| (*p >= '0' && *p <= '9') || *p == '.' || *p == '/'
#ifdef _WIN32
|| *p == ':'
#endif
) {
*(r++) = *p;
}
else {
/* percent-encode anything else */
*(r++) = '\\'; /* must escape it because it's TeX */
*(r++) = '%';
*(r++) = hex[(*p >> 4) & 0x0FU];
*(r++) = hex[*p & 0x0FU];
}
}
*r = '\0';
free(filename);
return result;
}
int main(int argc, char **argv)
{
int c;
char *input_file_name = NULL;
char *output_file_name = NULL;
char *output_basename = NULL;
char *error_file_name = NULL;
FILE *input_file = NULL;
FILE *output_file = NULL;
FILE *error_file = NULL;
gregorio_file_format input_format = FORMAT_UNSET;
gregorio_file_format output_format = FORMAT_UNSET;
gregorio_verbosity verb_mode = 0;
bool deprecation_errors = false;
bool point_and_click = false;
char *point_and_click_filename = NULL;
bool debug = false;
bool must_print_short_usage = false;
int option_index = 0;
static struct option long_options[] = {
{"output-file", 1, 0, 'o'},
{"stdout", 0, 0, 'S'},
{"output-format", 1, 0, 'F'},
{"messages-file", 1, 0, 'l'},
{"input-format", 1, 0, 'f'},
{"stdin", 0, 0, 's'},
{"help", 0, 0, 'h'},
{"version", 0, 0, 'V'},
{"licence", 0, 0, 'L'},
{"verbose", 0, 0, 'v'},
{"all-warnings", 0, 0, 'W'},
{"deprecation-errors", 0, 0, 'D'},
{"point-and-click", 0, 0, 'p'},
{"debug", 0, 0, 'd'},
};
gregorio_score *score = NULL;
gregorio_support_init("gregorio", argv[0]);
if (argc == 1) {
fprintf(stderr, "%s: missing file operand.\n", argv[0]);
print_short_usage(argv[0]);
gregorio_exit(0);
}
setlocale(LC_CTYPE, "C");
while (1) {
c = getopt_long(argc, argv, "o:SF:l:f:shOLVvWDpd",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'o':
if (output_file_name) {
fprintf(stderr,
"warning: several output files declared, %s taken\n",
output_file_name);
must_print_short_usage = true;
break;
}
if (output_file) { /* means that stdout is defined */
fprintf(stderr,
"warning: can't write to file and stdout, writing on stdout\n");
must_print_short_usage = true;
break;
}
output_file_name = optarg;
break;
case 'S':
if (output_file_name) {
fprintf(stderr,
"warning: can't write to file and stdout, writing on %s\n",
output_file_name);
must_print_short_usage = true;
break;
}
if (output_file) { /* means that stdout is defined */
fprintf(stderr, "warning: option used several times: %c\n", c);
must_print_short_usage = true;
break;
}
output_file = stdout;
break;
case 'F':
if (output_format) {
fprintf(stderr,
"warning: several output formats declared, first taken\n");
must_print_short_usage = true;
break;
}
if (!strcmp(optarg, GABC_STR)) {
output_format = GABC;
break;
}
if (!strcmp(optarg, GTEX_STR)) {
output_format = GTEX;
break;
}
if (!strcmp(optarg, DUMP_STR)) {
output_format = DUMP;
break;
} else {
fprintf(stderr, "error: unknown output format: %s\n", optarg);
print_short_usage(argv[0]);
gregorio_exit(1);
}
break;
case 'l':
if (error_file_name) {
fprintf(stderr,
"warning: several error files declared, %s taken\n",
error_file_name);
must_print_short_usage = true;
break;
}
error_file_name = optarg;
break;
case 'f':
if (input_format) {
gregorio_set_error_out(error_file);
fprintf(stderr,
"warning: several output formats declared, first taken\n");
must_print_short_usage = true;
break;
}
if (!strcmp(optarg, GABC_STR)) {
input_format = GABC;
break;
} else {
fprintf(stderr, "error: unknown input format: %s\n", optarg);
print_short_usage(argv[0]);
gregorio_exit(1);
}
break;
case 's':
/* input_file_name will be null here because of the way
* we use getopt_long */
assert(!input_file_name);
if (input_file) { /* means that stdin is defined */
fprintf(stderr, "warning: option used several times: %c\n", c);
must_print_short_usage = true;
break;
}
input_file = stdin;
break;
case 'h':
print_usage(argv[0]);
gregorio_exit(0);
break;
case 'V':
gregorio_print_version(copyright);
gregorio_exit(0);
break;
case 'v':
if (verb_mode && verb_mode != VERBOSITY_WARNING) {
fprintf(stderr, "warning: verbose option passed several times\n");
must_print_short_usage = true;
break;
}
verb_mode = VERBOSITY_INFO;
break;
case 'W':
if (verb_mode == VERBOSITY_WARNING) {
fprintf(stderr,
"warning: all-warnings option passed several times\n");
must_print_short_usage = true;
break;
}
if (verb_mode != VERBOSITY_INFO) {
verb_mode = VERBOSITY_WARNING;
}
break;
case 'D':
if (deprecation_errors) {
fprintf(stderr, "warning: deprecation-errors option passed "
"several times\n");
must_print_short_usage = true;
break;
}
deprecation_errors = true;
break;
case 'L':
print_licence();
gregorio_exit(0);
break;
case 'p':
if (point_and_click) {
fprintf(stderr,
"warning: point-and-click option passed several times\n");
must_print_short_usage = true;
break;
}
point_and_click = true;
break;
case 'd':
if (debug) {
fprintf(stderr,
"warning: debug option passed several times\n");
must_print_short_usage = true;
break;
}
debug = true;
break;
case '?':
must_print_short_usage = true;
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
gregorio_fail2(main, "unknown option: %c", c);
print_short_usage(argv[0]);
gregorio_exit(1);
break;
/* LCOV_EXCL_STOP */
}
} /* end of while */
if (optind == argc) {
if (!input_file) { /* input not undefined (could be stdin) */
fprintf(stderr, "%s: missing file operand.\n", argv[0]);
print_short_usage(argv[0]);
gregorio_exit(1);
}
} else {
input_file_name = argv[optind++];
output_basename = get_base_filename(input_file_name);
if (input_file) {
fprintf(stderr,
"warning: can't read from both stdin and a file, reading from %s\n",
input_file_name);
input_file = NULL;
must_print_short_usage = true;
}
}
if (optind < argc) {
must_print_short_usage = true;
fprintf(stderr, "ignored arguments:");
for (; optind < argc; ++optind) {
fprintf(stderr, " %s", argv[optind]);
}
fprintf(stderr, "\n");
}
if (must_print_short_usage) {
print_short_usage(argv[0]);
fprintf(stderr, "Proceeding anyway...\n");
}
gregorio_set_debug_messages(debug);
gregorio_set_deprecation_errors(deprecation_errors);
if (!input_format) {
input_format = DEFAULT_INPUT_FORMAT;
}
if (!output_format) {
output_format = DEFAULT_OUTPUT_FORMAT;
}
/* then we act... */
if (!output_file_name && !output_file) {
if (!output_basename) {
output_file = stdout;
} else {
switch (output_format) {
case GABC:
output_file_name =
get_output_filename(output_basename, "gabc");
break;
case GTEX:
output_file_name =
get_output_filename(output_basename, "gtex");
break;
case DUMP:
output_file_name =
get_output_filename(output_basename, "dump");
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
fprintf(stderr, "error: unsupported format");
print_short_usage(argv[0]);
gregorio_exit(1);
/* LCOV_EXCL_STOP */
}
}
}
if (output_basename) {
free(output_basename);
}
if (!output_file) {
if (!input_file) {
check_input_clobber(input_file_name, output_file_name);
}
gregorio_check_file_access(write, output_file_name, ERROR,
gregorio_exit(1));
output_file = fopen(output_file_name, "wb");
if (!output_file) {
fprintf(stderr, "error: can't write in file %s", output_file_name);
gregorio_exit(1);
}
}
/* we always have input_file or input_file_name */
if (input_file) {
if (point_and_click) {
fprintf(stderr,
"warning: disabling point-and-click since reading from stdin\n");
}
} else {
gregorio_check_file_access(read, input_file_name, ERROR,
gregorio_exit(1));
input_file = fopen(input_file_name, "r");
if (!input_file) {
fprintf(stderr, "error: can't open file %s for reading\n",
input_file_name);
gregorio_exit(1);
}
if (point_and_click) {
point_and_click_filename = encode_point_and_click_filename(
input_file_name);
}
}
if (!error_file_name) {
error_file = stderr;
gregorio_set_error_out(error_file);
} else {
gregorio_check_file_access(write, error_file_name, ERROR,
gregorio_exit(1));
error_file = fopen(error_file_name, "wb");
if (!error_file) {
fprintf(stderr, "error: can't open file %s for writing\n",
error_file_name);
gregorio_exit(1);
}
gregorio_set_error_out(error_file);
}
if (!verb_mode) {
verb_mode = VERBOSITY_DEPRECATION;
}
gregorio_set_verbosity_mode(verb_mode);
switch (input_format) {
case GABC:
score = gabc_read_score(input_file, point_and_click);
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
fprintf(stderr, "error : invalid input format\n");
fclose(input_file);
fclose(output_file);
gregorio_exit(1);
break;
/* LCOV_EXCL_STOP */
}
fclose(input_file);
if (score == NULL) {
/* score should never be NULL on return from gabc_read_score */
/* LCOV_EXCL_START */
fclose(output_file);
fprintf(stderr, "error in file parsing\n");
gregorio_exit(1);
/* LCOV_EXCL_STOP */
}
switch (output_format) {
case GABC:
gabc_write_score(output_file, score);
break;
case GTEX:
gregoriotex_write_score(output_file, score, point_and_click_filename);
break;
case DUMP:
dump_write_score(output_file, score);
break;
default:
/* not reachable unless there's a programming error */
/* LCOV_EXCL_START */
fprintf(stderr, "error : invalid output format\n");
gregorio_free_score(score);
fclose(output_file);
gregorio_exit(1);
break;
/* LCOV_EXCL_STOP */
}
fclose(output_file);
if (point_and_click_filename) {
free(point_and_click_filename);
}
gregorio_free_score(score);
gregorio_vowel_tables_free();
gabc_score_determination_lex_destroy();
gabc_notes_determination_lex_destroy();
gregorio_vowel_rulefile_lex_destroy();
if (error_file_name) {
fclose(error_file);
}
gregorio_exit(gregorio_get_return_value());
} /* due to exit on prior line, this will never be reached; LCOV_EXCL_LINE */