// tabout.C Version 7.41  Copyleft (c) 杉本典夫
// Time-stamp: <2025-11-03 16:56:59 tac>
//
// DANSコマンドの出力結果を一覧表形式で出力する。
//
// 使用ライブラリ:libdans.so, libfun.so
// コンパイル法 :g++ -O -s -W tabout tabout.C
//              -I/usr/local/include -L/usr/local/lib -ldans -lfun

#include <dans.H>               // dans用ヘッダファイル(fun.H含む)

using namespace dans;           // dans名前空間使用(名前空間matvec含む)

const vecstr USAGE =            // プログラム使用法表示用
{
  "tabout  Version 7.41         Copyleft(Vo\\oV) 2025 杉本典夫",
  "書式:tabout -f=<書式指定ファイル名> [<オプション> …] [<データファイル> …]",
  "機能:DANSコマンドの出力結果を一覧表形式で出力します。",
  "<オプション>",
  "  -b=<ブランク文字列>:ヌル文字列を指定された文字列に変換して出力します。",
  "  -f=<ファイル名>:指定したファイルからオプションを読み込みます。",
  "  -fmtf=<書式指定ファイル名>:指定したファイルから出力書式を読み込みます。",
  "  -h[=<コマンド名>]:オンラインマニュアルを表示します。(-h:-h=man)"
};

enum fmttype                    // 書式の種類
{
  fmtnon,                       // なし
  sdata,                        // 文字データ
  sitem,                        // 文字項目データ
  ditem,                        // 整数項目データ
  fitem,                        // 実数項目データ
  newl,                         // 改行指定
  fmtetc                        // その他
};

class fmt                       // 一覧表形式の書式クラス
{
private:
  fmttype v_type;               // 書式の種類
  int v_sno;                    // 層別項目番号
  string v_sstr;                // 層別文字列
  int v_ino;                    // 項目番号
  string v_fstr;                // 書式文字列
  int v_nt;                     // 全出力桁数
  int v_nf;                     // 小数点以下の桁数
  fmttype setfmt(const string& str); // 文字列で書式を設定する
public:
  fmt() : v_type(fmtnon), v_sno(0), v_sstr(), v_ino(0), v_fstr(), v_nt(0), v_nf(0)
  // デフォールトコンストラクタ
  {}
  fmt(const fmt& fmt0) : v_type(fmt0.v_type), v_sno(fmt0.v_sno), v_sstr(fmt0.v_sstr), v_ino(fmt0.v_ino), v_fstr(fmt0.v_fstr), v_nt(fmt0.v_nt), v_nf(fmt0.v_nf)
  // コピーコンストラクタ
  {}
  virtual ~fmt()                // デストラクタ
  {}
  const fmt& operator=(const fmt& fmt0); // コピー代入演算子
  fmttype type() const           // 書式の種類を返す
  { return v_type; }
  fmttype type(fmttype type0)   // 書式の種類を設定する
  { return (v_type = type0); }
  int sno() const               // 層別項目番号を返す
  { return v_sno; }
  const string& sstr() const    // 層別文字列を返す
  { return v_sstr; }
  int ino() const               // 項目番号を返す
  { return v_ino; }
  const string& fstr() const    // 書式文字列を返す
  { return v_fstr; }
  int nt() const                // 全出力桁数を返す
  { return v_nt; }
  int nf() const                // 小数点以下の桁数を返す
  { return v_nf; }
  fmttype setstr(const string& str0); // 文字列による文字データの設定
  fmttype setid(idd& id0);      // データ識別情報による書式の設定
};

const fmt& fmt::operator=(const fmt& fmt0) // 代入演算子
{
  if (&fmt0 == this) return *this; // 自分自身への代入を防ぐ
  v_type = fmt0.v_type;
  v_sno = fmt0.v_sno;  v_sstr = fmt0.v_sstr;
  v_ino = fmt0.v_ino;  v_fstr = fmt0.v_fstr;
  v_nt = fmt0.v_nt;  v_nf = fmt0.v_nf;
  return *this;
}

fmttype fmt::setfmt(const string& str) // 文字列で書式を設定する
{
  if (str.empty() || str[0] != '%') // 書式不適切
  {
    v_type = fmtnon;
    return v_type;
  }
  string tstr(str), substr;
  tstr.erase(0, 1);
  strsize_t sidx = tstr.size();
  char tchar = tstr[sidx - 1];
  if (tchar == 's') v_type = sitem;
  else if (tchar == 'd') v_type = ditem;
  else if (tchar == 'f') v_type = fitem;
  else v_type = fmtnon;
  if (v_type == fmtnon) return v_type; // 書式不適切
  v_fstr = str;
  sidx = getsubstr(substr, tstr, '.');
  char* ptr = nullptr;
  v_nt = strtol(substr.c_str(), &ptr, 0);
  if (v_nt == 0)                // 全桁指定不適切
  {
    v_type = fmtnon;
    return v_type;
  }
  if (sidx < tstr.size())       // 小数部桁指定がある時
  {
    if (v_type != fitem)        // 小数部桁指定は実数タイプだけ
    {
      v_type = fmtnon;
      return v_type;
    }
    tstr.erase(0, sidx + 1);  ptr = nullptr;
    v_nf = strtol(tstr.c_str(), &ptr, 0);
    if (v_nf < 0 || ptr == tstr.c_str()) // 小数部桁指定不適切
    {
      v_type = fmtnon;
      return v_type;
    }
  }
  return v_type;                 // 書式のタイプを返す
}

fmttype fmt::setstr(const string& str0) // 文字列による文字データの設定
{
  v_type = sdata;  v_fstr = str0;
  v_nt = strclen(str0, CHARwide);  v_nf = 0;
  return v_type;                // 書式のタイプを返す
}

fmttype fmt::setid(idd& id0)    // データ識別情報による書式の設定
{
  char* ptr = nullptr;
  if (id0.ns() > 0)
  {
    string opt[2];              // オプションのopt[0]:項目,opt[1]:内容
    if (getopt(opt, id0.estra(0).pcdata(), OPTdev) < 1)
    { v_type = fmtnon; }
    else
    {
      v_sno = strtol(opt[0].substr(0).c_str(), &ptr, 0);
      if (v_sno < 1) v_type = fmtnon;
      else { v_type = fmtetc;  v_sstr = opt[1]; }
    }
  }
  if (v_type == fmtnon) return v_type; // 層別指定不適切
  if (id0.ni() > 0)
  {
    string opt[2];              // オプションのopt[0]:項目,opt[1]:内容
    if (getopt(opt, id0.item(0), OPTdev) < 1)
    { v_type = fmtnon; }
    else
    {
      ptr = nullptr;
      v_ino = strtol(opt[0].substr(0).c_str(), &ptr, 0);
      if (v_ino < 1) v_type = fmtnon;
      else setfmt(opt[1]);
    }
  }
  return v_type;                // 書式のタイプを返す
}

using lfmt = list<fmt>;         // 書式リストの別名定義

class option                    // オプション集合体
{
private:
  string v_path;                // プログラムのパス
  string v_pname;               // プログラム名
  string v_blank;               // ブランク文字列
  lfmt v_fmtlst;                // 書式リスト
  int fget_fmt(const string& fname); // 書式をファイルから読み込む
  strsize_t get_fmtstr(string& lfmtstr, const string& str) const;
  // 書式または文字列をひとつ取り出す
public:
  option(const char* c_pname = nullptr); // コンストラクタ
  ~option()                              // デストラクタ
  {}
  const string& path() const    // プログラムのパスを返す
  { return v_path; }
  const string& pname() const   // プログラム名を返す
  { return v_pname; }
  const string& blank() const   // ブランク文字列を返す
  { return v_blank; }
  const lfmt& fmtlst() const    // 書式リストを返す
  { return v_fmtlst; }
  void setdef()                 // 省略時オプション指定を設定する
  { getoptdef(*this); }
  void set(const string& optstr, char devc = OPTdev);
  // オプション指定によってオプションを設定する
  int check(int ni) const;     // オプションのチェックとセット
};

option::option(const char* c_pname) : v_path(), v_pname(), v_blank(BLANKs), v_fmtlst()
// コンストラクタ
{
  if (c_pname == nullptr) return; // 初期設定せず
  v_pname = getname(c_pname);     // パス名なしのプログラム名取得
  v_path = c_pname;
  strsize_t idx = v_path.find(v_pname);
  if (idx != string::npos) v_path.erase(idx); // プログラム名削除
}

void option::set(const string& optstr, char devc)
// オプション指定によってオプションを設定する
{
  string opt[2];                // オプションのopt[0]:項目,opt[1]:内容
  if (getopt(opt, optstr, devc) < 1) return; // オプション指定なし
  int err = 0;
  if (opt[0] == "b")            // ブランク文字列指定
  {
    v_blank = BLANKs;
    if (!(opt[1].empty()))
    {
      sstrtostr(opt[1]);
      v_blank = opt[1];
    }
  }
  else if (opt[0] == "f")       // オプション指定ファイル
  { err = fgetopt(*this, opt[1], true); }
  else if (opt[0] == "fmtf")    // 書式指定ファイル
  { err = fget_fmt(opt[1]); }
  else if (opt[0] == "h")       // オンラインヘルプ表示
  { err = help(pname(), USAGE, opt[1]); }
  else                          // 間違ったオプション指定
  { err = nonopt(pname(), optstr, USAGE); }
  if (err > 0) throw help_except("ヘルプ表示");
  if (err < 0) throw option_error(string("オプション指定違反→") + optstr);
}

int option::fget_fmt(const string& fname) // 書式をファイルから読み込む
{
  ifstream fin(fname.c_str());
  if (!fin) return disperr(pname(), "書式指定ファイルがありません→", fname);
  string sline;
  int err = 0;
  while (err == 0 && getsline(sline, fin) != EOF)
  {
    strchomp(sline);
    strsize_t sidx = string::npos;
    string fmtstr;
    while (err == 0 && (sidx = get_fmtstr(fmtstr, sline)) != string::npos)
    {
      fmt tfmt;
      if (fmtstr[0] == CHRtags) // 書式
      {
        idd id;
        if (id.append(fmtstr) < 0)
        { err = disperr(pname(), "書式が間違ってます→", fmtstr); }
        else if (id.ni() == 0)  // 層別情報の時
        {
          sline.erase(0, sidx);
          sidx = get_fmtstr(fmtstr, sline);
          if (sidx != string::npos)
          {
            if (id.append(fmtstr) > 0)
            {
              if (tfmt.setid(id) == fmtnon) err = disperr(pname(), "書式が間違ってます→", fmtstr);
            }
            else
            { err = disperr(pname(), "書式が間違ってます→", fmtstr); }
          }
        }
        else                    // 項目情報の時
        {
          if (tfmt.setid(id) == fmtnon) err = disperr(pname(), "書式が間違ってます→", fmtstr);
        }
      }
      else                      // 文字列
      {
        if (tfmt.setstr(fmtstr) == fmtnon) err = disperr(pname(), "文字列が変です→", fmtstr);
      }
      if (err == 0)
      {
        v_fmtlst.push_back(tfmt);
        if (sidx < sline.size()) sline.erase(0, sidx);
        else break;             // 文字列終了
      }
    }
    if (err == 0)
    {
      fmt tfmt;
      tfmt.type(newl);          // 最後に改行を追加する
      v_fmtlst.push_back(tfmt);
    }
  }
  return err;                   // err=0:エラーなし
}

strsize_t option::get_fmtstr(string& fmtstr, const string& str) const
// 書式または文字列をひとつ取り出す
{
  if (str.empty()) return string::npos;
  strsize_t sidx = string::npos;
  if (str[0] == CHRtags)        // 開始タグ
  {
    sidx = getsubstr(fmtstr, str, CHRetag);
    if (sidx == string::npos || sidx >= str.size()) return string::npos;
    string tstr;
    sidx += getsubstr(tstr, str.substr(sidx), CHRtage);
    if (sidx == string::npos || sidx >= str.size()) return string::npos;
    fmtstr += tstr;
    fmtstr += CHRtage;  ++sidx;
  }
  else                          // 文字列
  { sidx = getsubstr(fmtstr, str, CHRtags); }
  return sidx;                  // 次の書式文字列へのポインターを返す
}

int option::check(int ni) const // オプションのチェックとセット
{
  if (v_fmtlst.empty()) return disperr(pname(), "書式指定がないので処理できません!");
  for (auto fmti : v_fmtlst)
  {
    if (fmti.sno() > ni) return disperr(pname(), "書式中の層別項目指定が不適切です!");
    if (fmti.ino() > ni) return disperr(pname(), "書式中の項目指定が不適切です!");
  }
  return 0;                     // エラーなし
}

class taboutclass               // taboutメインクラス
{
private:
  string fname;                 // ファイル名
  bool setdata(string& strdata, double& fdata, const fmt& fmti, ldata& datalst) const;
  // 書式に一致するデータをサーチして設定する
public:
  taboutclass() : fname()         // コンストラクタ
  {}
  ~taboutclass()                  // デストラクタ
  {}
  int get(const char* c_fname, const option& opt, idd& id, ldata& datalst);
  // データを読み込みメモリに保存する
  void put(const option& opt, idd& id, ldata& datalst) const;
  // データを出力する
};

bool taboutclass::setdata(string& strdata, double& fdata, const fmt& fmti, ldata& datalst) const
// 書式に一致するデータをサーチして設定する
{
  stra tstra;
  tstra.get('s', fmti.sno(), fmti.sstr());
  bool hit = false;
  for (ldata_itr dpos = datalst.begin(); hit == false && dpos != datalst.end(); ++dpos)
  {
    if (fmti.sno() < 1)         // 層別情報がない時
    { hit = true; }
    else
    {
      if (tstra.match(*dpos) == true) hit = true;
    }
    if (hit)
    {
      int didx = fmti.ino() - 1;
      strdata = dpos->sdata(didx);  fdata = dpos->fdata(didx);
    }
  }
  return hit;                   // hitを返す
}

int taboutclass::get(const char* c_fname, const option& opt, idd& id, ldata& datalst)
// データを読み込みメモリに保存する
{
  if (c_fname == nullptr) fname.erase();
  else fname = c_fname;
  int nc = fgetdata(fname, id, datalst);
  if (nc < 0) throw runtime_error("実行時エラー！");
  if (opt.check(id.ni()) < 0) throw option_error("実行時オプションエラー！");
  return nc;
}

void taboutclass::put(const option& opt, idd& id, ldata& datalst) const
// データを出力する
{
  for (lfmt::const_iterator fmtitr = (opt.fmtlst()).begin(); fmtitr != (opt.fmtlst()).end(); ++ fmtitr)
  {
    string strdata(BLANKs);
    double fdata = BLANKf;
    int npad = 0;
    if (fmtitr->type() == sitem || fmtitr->type() == ditem || fmtitr->type() == fitem)
    {
      if (setdata(strdata, fdata, *fmtitr, datalst))
      { npad = strdata.size() - strclen(strdata, CHARwide); }
    }
    switch(fmtitr->type())
    {
    case sdata : cout << fmtitr->fstr(); // 文字列
      break;
    case sitem :                // 文字データ
      if (strdata == BLANKs)
      {
        npad = opt.blank().size() - strclen(opt.blank(), CHARwide);
        cout << setw(fmtitr->nt() + npad) << opt.blank();
      }
      else
      { cout << setw(fmtitr->nt() + npad) << strdata; }
      break;
    case ditem :                // 整数データ
      if (fdata == BLANKf)
      {
        npad = opt.blank().size() - strclen(opt.blank(), CHARwide);
        cout << setw(fmtitr->nt() + npad) << opt.blank();
      }
      else
      { cout << setw(fmtitr->nt()) << (int)fdata; }
      break;
    case fitem :                // 実数データ
      if (fdata == BLANKf)
      {
        npad = opt.blank().size() - strclen(opt.blank(), CHARwide);
        cout << setw(fmtitr->nt() + npad) << opt.blank();
      }
      else
      { cout << setw(fmtitr->nt()) << fixed << setprecision(fmtitr->nf()) << fdata; }
      break;
    case newl : cout << endl;   // 改行
    }
  }
}

int main(int argc, char* argv[]) // メインルーチン
{
  taboutclass tabout;           // メインクラス変数
  option opt(argv[0]);          // オプション
  idd id;                       // データ識別情報
  ldata datalst;                // データリスト
  try
  {
    opt.setdef();               // オプションの初期設定
    bool fok = true;            // ファイル処理フラッグ
    for (int i = 1; i <= argc; ++i)
    {
      if (i >= argc)
      {
        if (fok) tabout.get(nullptr, opt, id, datalst); // 標準入力から読み込む
      }
      else if (*(argv[i]) == CHRopt)
      { opt.set(argv[i] + 1);  fok = true; }
      else
      { tabout.get(argv[i], opt, id, datalst);  fok = false; }
    }
    tabout.put(opt, id, datalst);
  }
  catch (help_except& helpex)   // ヘルプファイル表示
  { return 0; }
  catch (option_error& opterr)  // オプション指定エラー
  { return 1; }
  catch (runtime_error& rterr)  // 実行時エラー
  {
    disperr(opt.pname(), rterr.what());
    return 2;
  }
  catch (...)                   // 予期せぬ例外
  {
    disperr(opt.pname(), "内容不明なエラー発生!");
    return 255;
  }
  return 0;
}
