////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2025 DaqGroup daqgroup@mail.ru under MIT license        //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - addon user program. //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Read sections or named parameters from INI or configuration files.         //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20250313 - Created by A.K.                                                 //
////////////////////////////////////////////////////////////////////////////////

program readcfg;

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$R *.res}

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math, graphics, interfaces, forms,
 _crw_alloc, _crw_cmdargs, _crw_ef, _crw_str, _crw_fio;

const
 nvSep : LongString = ' = ';  // Name = Value separator
 pMode : Integer    = 1;      // 1: drop extra spaces

function GetVersionInfo(const Name:LongString):LongString;
begin
 Result:=CookieScan(GetFileVersionInfoAsText(ProgName),Name);
end;
function DotEnding(const S:LongString):LongString;
const dot='.';
begin
 Result:=S;
 if (Result<>'') then if (StrFetch(Result,Length(Result))<>dot) then Result:=Result+dot;
end;
procedure PrintVersionInfo(const Fallback:LongString; Verb:Boolean=false);
begin
 if not IsEmptyStr(GetVersionInfo('ProductName')) then begin
  writeln(DotEnding(GetVersionInfo('ProductName')+' version '+GetVersionInfo('ProductVersion')));
  if Verb then writeln(DotEnding(GetVersionInfo('LegalCopyright')));
  if Verb then writeln(DotEnding(GetVersionInfo('FileDescription')));
 end else begin
  writeln(Fallback);
 end;
end;
procedure PrintVersion(Verb:Boolean=false);
begin
 PrintVersionInfo(ProgBaseName+' - Read configuration (*.ini,*.cfg) file.',Verb);
end;

procedure PrintHelp;
var exe,msg:LongString;
begin
 PrintVersion(True);
 exe:=ProgBaseName;
 msg:='Purpose:'+EOL
     +' Read parameters or sections from ini or configuration file.'+EOL
     +' Expected INI file format:'+EOL
     +'  [Section]'+EOL
     +'  Key = Value'+EOL
     +'Syntax:'+EOL
     +' '+exe+' [-f] cfg [-s] sec [[-k] key]'+EOL
     +'Options:'+EOL
     +' -f cfg  : set file name (cfg)'+EOL
     +' -s sec  : set section name (sec)'+EOL
     +' -k key  : set key name (key)'+EOL
     +' -v      : print only values'+EOL
     +'Notes:'+EOL
     +'Example:'+EOL
     +' '+exe+' file.ini [section]                - read section'+EOL
     +' '+exe+' file.ini [section] key            - read key=value'+EOL
     +' '+exe+' -f file.cfg -s section -k key     - read key=value'+EOL
     +' '+exe+' -v -f file.cfg -s section -k key  - read only value'+EOL;
 write(msg);
end;

function rdcfgIter(n:SizeInt; Line:LongString; Custom:Pointer):Boolean;
var sn,sv:LongString;
begin
 Result:=true;
 if IsEmptyStr(Line) then Exit; sn:=''; sv:='';
 if (ExtractNameValuePair(Line,sn,sv)=0) then Exit;
 if HasFlags(pMode,1) then writeln(sn,nvSep,sv) else writeln(Line);
end;

procedure exec_readcfg;
var cfg,sec,key,val:LongString; argc:Integer;
var fop,sop,kop:Boolean; fnp,snp,knp:Integer;
const sopts='-s;-sec;--sec;-sect;--sect;-section;--section;';
const vopts='-v;-val;--val;-value;--value';
const fopts='-f;-file;--file;';
const kopts='-k;-key;--key;';
 function HasOpt(opts:LongString):Boolean;
 var i:Integer; opt:LongString;
 begin
  for i:=1 to WordCount(opts,ScanSpaces) do begin
   opt:=ExtractWord(i,opts,ScanSpaces);
   if CmdArgs.HasOption(opt)
   then Exit(True);
  end;
  Result:=False;
 end;
 function GetOptVal(opts:LongString; var s:LongString):Boolean;
 var i:Integer; opt:LongString;
 begin
  for i:=1 to WordCount(opts,ScanSpaces) do begin
   opt:=ExtractWord(i,opts,ScanSpaces);
   if CmdArgs.HasOption(opt) then begin
    s:=CmdArgs.GetOptionValue(opt);
    Exit(True);
   end;
  end;
  Result:=False;
 end;
begin
 cfg:=''; sec:=''; key:=''; val:='';
 argc:=CmdArgs.Count-1;
 if (argc=0) then begin
  writeln(StdErr,'Error: No arguments specified. Type '+ProgBaseName+' -h for help.');
  ExitCode:=1;
  Exit;
 end;
 CmdArgs.ListOptVal:=fopts+sopts+kopts;
 // --version
 if HasOpt('-version;--version;') then begin
  PrintVersion;
  Exit;
 end;
 // --help
 if HasOpt('-h;-help;--help;') then begin
  PrintHelp;
  Exit;
 end;
 // -f file
 fop:=GetOptVal(fopts,cfg); fnp:=BoolToInt(not fop);
 if (cfg='') and CmdArgs.HasParam(fnp) then cfg:=CmdArgs.GetParam(fnp);
 if (cfg='') then begin
  writeln(StdErr,'Error: file name is not specified.');
  ExitCode:=1;
  Exit;
 end;
 cfg:=UnifyFileAlias(cfg);
 if not FileIsReadable(cfg) then begin
  writeln(StdErr,'Error: file not found: '+cfg);
  ExitCode:=1;
  Exit;
 end;
 // -s section
 sop:=GetOptVal(sopts,sec); snp:=fnp+BoolToInt(not sop);
 if (sec='') and CmdArgs.HasParam(snp) then sec:=CmdArgs.GetParam(snp);
 if (sec='') then begin
  writeln(StdErr,'Error: section name is not specified.');
  ExitCode:=1;
  Exit;
 end;
 sec:=UnifySection(sec);
 // -k key
 kop:=GetOptVal(kopts,key); knp:=snp+BoolToInt(not kop);
 if (key='') and CmdArgs.HasParam(knp) then key:=CmdArgs.GetParam(knp);
 // Read section or value
 if (key='') then begin
  val:=ExtractTextSection(cfg,sec,efConfigNC);
  if (val<>'') then ForEachStringLine(val,rdcfgIter,nil);
 end else
 if ReadIniFileString(cfg,sec,key,val,efConfigNC,svConfig) then begin
  if HasOpt(vopts) then begin
   if (val<>'') then writeln(val);
  end else begin
   writeln(key,nvSep,val);
  end;
 end;
end;

begin
 try
  exec_readcfg;
 except
  on E:Exception do BugReport(E,nil,'readcfg');
 end;
end.

//////////////
// END OF FILE
//////////////
