/*
 *	error.c
 *
 *	エラー書き出し関数
 *
 *	* Mon Sep 28 21:01:46 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 'GNU拡張のエラー書き出し関数'互換モジュールです。使用方法は下記のページを参照してください。
 *	  「Linux Programmer's Manual - ERROR」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/error.3.html)
 *	* Sat Oct 24 14:03:11 JST 2015 Naoyuki Sawa
 *	- program_invocation_name,program_invocation_short_nameによるプログラム名の出力を有効にしました。
 *	  これまでは、program_invocation_name,program_invocation_short_nameによるプログラム名の出力は行っていなかったのですが、
 *	  本日、/clip/include/errno.h にprogram_invocation_name,program_invocation_short_nameを追加して、可能になったからです。
 *	  詳細は、/clip/include/errno.h の同日のコメントを参照して下さい。
 *	* Mon Oct 26 20:30:31 JST 2015 Naoyuki Sawa
 *	- error(),及び,error_at_line()が、errnumに0が指定されると改行を出力していなかったバグを修正しました。
 */
#include "clip.h"
/*****************************************************************************
 *	グローバル変数
 *****************************************************************************/
int error_one_per_line;		//大域変数error_one_per_lineが0以外に設定されている場合、filenameとlinenumの値が共に等しいerror_at_line()の呼び出しは、1つの(最初の)メッセージの出力にまとめられる。
unsigned error_message_count;	//大域変数error_message_countは、error()とerror_at_line()で出力されたメッセージの数を表す。
void (*error_print_progname)();	//大域変数error_print_prognameに関数のアドレスが割り当てられている場合(つまりNULLではない場合)、メッセージの前にプログラム名とコロンを書き出すのではなく、この関数を呼び出す。この関数は標準エラー出力(stderr)に対しての適切な文字列を書き出さなければならない。
/*****************************************************************************
 *	ローカル関数
 *****************************************************************************/
static void error_at_line_subr(int status, int errnum, const char* filename, unsigned linenum, const char* format, va_list ap) {
	if(error_one_per_line && filename) {
		static char*    old_filename;
		static unsigned old_linenum;
		if(old_filename && !strcmp(old_filename, filename) && (old_linenum == linenum)) { goto L_SKIP; }
		free(old_filename);
		if(!(old_filename = strdup(filename))) { DIE(); }
		     old_linenum  =        linenum;
	}
	error_message_count++;
	if(error_print_progname) {							//error_print_prognameが設定されていたら、error_print_prognameを呼び出してプログラム名を表示する。					//┐
		(*error_print_progname)();																							//│
	} else if(program_invocation_short_name && *program_invocation_short_name) {	//program_invocation_short_nameが非空文字に設定されていたら、program_invocation_short_nameを使って既定の書式でプログラム名を表示する。	//│error_print_prognameを最優先とする事は、error()の関数仕様です。
		fprintf(stderr, "%s: ", program_invocation_short_name);																				//│error_print_prognameを最優先とする事以外は、関数仕様では定義されていないようです。
	} else if(program_invocation_name && *program_invocation_name) {		//program_invocation_nameが非空文字に設定されていたら、program_invocation_short_nameを使って既定の書式でプログラム名を表示する。	//│program_invocation_short_nameとprogram_invocation_nameの優先順,及び,いずれも設定されていなかった場合にプログラム名を表示しないようにしたのは、自分の判断でそうしました。
		fprintf(stderr, "%s: ", program_invocation_name);																				//│
	} else {									//上記のいずれも設定されていなければ、プログラム名,及び,": "を表示しない。								//┘
		/** no job **/
	}
	if(filename) { fprintf(stderr, "%s:%d: ", filename, linenum); }
	vfprintf(stderr, format, ap);
    //{{2015/10/26修正:error(),及び,error_at_line()が、errnumに0が指定されると改行を出力していなかったバグを修正しました。
    //	if(errnum) { fprintf(stderr, ": %s\n", strerror(errnum)); }
    //↓2015/10/26修正:error(),及び,error_at_line()が、errnumに0が指定されると改行を出力していなかったバグを修正しました。
	if(errnum) { fprintf(stderr, ": %s", strerror(errnum)); }
	fputc('\n', stderr);
    //}}2015/10/26修正:error(),及び,error_at_line()が、errnumに0が指定されると改行を出力していなかったバグを修正しました。
L_SKIP:
	if(status) { exit(status); }
}
/*****************************************************************************
 *	グローバル関数
 *****************************************************************************/
//□「Linux Programmer's Manual - ERROR」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/error.3.html)より引用:
//│error()は汎用的なエラー書き出し関数である。
//│この関数は標準出力(stdout)に書き出してから、標準エラー出力(stderr)に書き出す。
//│書き出す内容は、プログラム名・コロン・スペース・printf形式のフォーマット文字列formatで指定されたメッセージである。	─当実装では自動的なプログラム名の出力を省略しました。
//│errnumが0以外の場合、2つ目のコロンとスペースの後にstrerror(errnum)で指定された文字列も書き出す。
//│formatに必要な任意の引き数が、引き数リストのformatの後に続く。
//│出力の終端には改行文字が付く。
//│error()で出力されるプログラム名は、大域変数program_invocation_nameの値である。					┐
//│program_invocation_nameの初期値はmain()のargv[0]の値と等しい。							├当実装では自動的なプログラム名の出力を省略しました。
//│この変数の値は変更可能であり、変更するとerror()の出力が変わる。							┘
//│statusが0以外の場合、error()はexitを呼び出して、指定された終了ステータスでプログラムを終了させる。
void error(int status, int errnum, const char* format, ...) {
	va_list ap;
	va_start(ap, format);
	error_at_line_subr(status, errnum, NULL, 0, format, ap);
	va_end(ap);
}
/*---------------------------------------------------------------------------*/
//□「Linux Programmer's Manual - ERROR」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/error.3.html)より引用:
//│error_at_line()関数は、引き数filenameとlinenumが追加されている以外は、error()と同じである。
//│生成される出力は、プログラム名の後にコロン・filenameの値・コロン・linenumの値が書き出される以外は、error()と同じである。
//│プリプロセッサの値__LINE__と__FILE__は、error_at_line()を呼び出すときに役に立つ。
//│その他のプリプロセッサの値も使うことができる。例えば、これらの引き数で入力ファイルにおける位置を参照できる。
void error_at_line(int status, int errnum, const char* filename, unsigned linenum, const char* format, ...) {
	va_list ap;
	va_start(ap, format);
	error_at_line_subr(status, errnum, filename, linenum, format, ap);
	va_end(ap);
}
