<?php
// This PHP script generates clickable HTML documentation files for amforth
// project. Script should be run once (offline) when new versions of sorces are
// available. It creates new subdirectory with HTML documentation tree.
//
// Default configuration is in file GenerateHTML.cfg
//
// Command Line paramaters are available via php GenerateHTML.php --help
//
// (c)miho 2007 / http://www.mlab.cz

// **********************************************************************
// History
// **********************************************************************
// 0.00 work wersion
// 0.01 basic functionality


// **********************************************************************
// Definitions/parameters
// **********************************************************************


define ('INCLUDE_PATTERN', "/^ *\.include +\"(\S+)\"/i");
define ('LABEL_PREFIX',    "(VE_|XT_|PFA)");
define ('LABEL_PATTERN',   "/^ *(".LABEL_PREFIX."\S+):/i");
define ('WORD_PATTERN',    "/((?:\\n *;.*)*)\\n *VE_Q:[^\\n]*\\n? *.db +[^,]* *, *(\S[^\\n|;]+)/im");  // Q is used instead of Word
define ('ASMFILES',        "CodeAsm/");


// **********************************************************************
// Generic Funcions
// **********************************************************************


function Error($ErrorStr)
// If ErrorStr is not empty print it and die
{
  if ($ErrorStr=="")
    return;
  print "\n";
  print "  ERROR: -->".$ErrorStr."\n";
  die(1);
}


function MyReadFile($FileName, &$FileContent)
// Reads file, returns file as a single string
{
  //
  if ($FileName=="")
    return "No File Name";

  // Read file
  $FileContent=@file($FileName);

  // Test if file contains any content
  if ($FileContent==FALSE)
    return "No Data in File: $FileName";

  // Remove CR or LF or CR/LF
  foreach($FileContent as $Key => $Line)
  {
    $FileContent[$Key]=rtrim($Line);
  }

  // No error
  return "";
}


function MyReadFileString($FileName, &$FileContent)
// Reads file and returns its content as a single string
{
  // No Error
  $Error="";
  
  // Read file
  $Error=MyReadFile($FileName, &$FileContent);

  // Convert to a single string
  if ($Error=="") $FileContent=implode("\n",$FileContent);

  // Finished
  return $Error;
}


function MyWriteFile($FileName, $FileContent)
// Creates and writes file
{
    // Create Output File
    $File=@fopen($FileName,"w");
    if ($File==FALSE)
    {
      return "Unable Write File ".$FileName;
    }
    
    // Write content
    fwrite($File,$FileContent);
    fclose($File);
    
    // No Error
    return "";
}


function FileName2HTML($FileName)
// Converts FileName to FileName of HTML file
{
  // Remove path
  $FileName=basename($FileName);
  // Change suffix
  $FileName=preg_replace("/\.asm$/i",".html",$FileName);
  // Finished
  #print $FileName;
  return $FileName;
}


function Label2Link($Link, $href, $title, &$LabelList)
// Converts Label to Link
// If label not found returns original text
// IN $Link      - label to find
// IN $Word      - word to show (on mouse)
// IN $LabelList - list of all labels (Label, FileName, LineNumber)
{
  // Find label in $LabelList
  foreach($LabelList as $Value)
  {
    if ($Link===$Value["Label"])
    {
      #$LabelListItem=$Value /////////////////////////////
      print "Found ".$Value["Label"]."\n";
    }
  }

  $FileName=$Value["FileName"]."#".$Link;
  
  // Create link
  $Link="<a href=\"".$FileName."\" title=\" ".$Word."\">".$Link.'</a>';

  return $Link;
}


// **********************************************************************
// Function for processing
// **********************************************************************


function Help()
// Display help
{
  print "\n";
  print "This script takes default parameters from GenerateHTML.cfg.\n";
  print "\n";
  print "Parameters can be entered directly on command line as well:\n";
  print "  php GenerateHTML.php par1=val1 par2=val2\n";
  print "\n";
  print "Sometimes (in Windows) it is necessary to use quotation marks this way:\n";
  print "  php GenerateHTML.php \"par1=val1\" \"par2=val2\"\n";
  print "\n";
  print "Parameters are: \n";
  print "  EnableApache={0|1}  - Enable to run in Apache\n";
  print "  SourceDir=path      - path to asm source directory\n";
  print "  SourceAsm=file      - name of top level asm source file\n";
  print "  SvnInfoFileName     - name of Svn Info File\n";
  print "  TemplateDir=path    - path to HTML template\n";
  print "  DestinationDir=path - path to destination HTML\n";
  Error("\n");
}


function Parameters(&$CFG)
// Function process Command Line parameters
{
  // Dummy values
  $CFG["EnableApache"]    =  0;     // Do not allow run in Apache
  $CFG["SourceDir"]       = "";
  $CFG["SourceAsm"]       = "";
  $CFG["SvnInfoFileName"] = "";
  $CFG["TemplateDir"]     = "";
  $CFG["DestinationDir"]  = "";
  // Default values (in cfg file)
  @include_once("GenerateHTML.cfg");
  // Command Line parameters
  if ($_SERVER["argc"]>1)
  {
    // Help
    if ($_SERVER["argv"][1]=="--help")
      Help();
    // Drop
    unset($_SERVER["argv"][0]);
    // Go through arguments
    foreach($_SERVER["argv"] as $Key => $Value)
    {
      $Left=substr($Value,0,strpos($Value,"="));
      $Right=substr($Value,strpos($Value,"=")+1);
      if (isset($CFG[$Left]) && $Right!="" )
      {
        $CFG[$Left]=$Right;
      }
      else
      {
        return "Invalid Parameter: $Value";
      }
    }
  }

  // Check if alowed to run in Apache
  if ($_SERVER["argc"]==0 & ! $CFG["EnableApache"])
    return "<b>This Script is configured so that it will not run in Apache.</b>";

  // Info
  print "Parameters\n";

  // Correct paths and existence
  foreach($CFG as $Key => $Value)
  {
    if (stripos($Key,"Dir"))
    {
      // Correct / at the end of path
      if (substr($Value,strlen($Value)-1)!="/")
      {
        $CFG[$Key]=$Value."/";
      }
      // Check existence
      #print "DIR".$CFG[$Key]."\n";
      if ( ! is_dir($CFG[$Key]))
      {
        return "Directory does not exist: ".$Key."=".$CFG[$Key];
      }
    }
  }

  // Print info
  if (count(CFG))
  foreach($CFG as $Key => $Value)
    print "  $Key=$Value\n";
  print "\n";

  // No Error
  return "";
}


function SourceAsm($SourceDir, $FileName, &$SourceAsmFiles, &$LabelList )
// Process ASM source file, recurse all includes
// Returns Error String
// Stores file names and labels into two arrays
// IN  $SourceDir      - base directory (not printed)
// IN  $FileName       - file to process
// OUT $SourceAsmFiles - list of all processed files (Filename)
// OUT $LabelList      - list of all labels (Label, FileName, LineNumber)
{

  // Start
  print "Read Asm: $FileName\n";

  // Read file
  $Error=MyReadFile($SourceDir.$FileName, $FileContent);
  if ($Error!="")
    return $Error;

  // Remember filename
  $SourceAsmFiles[]=$FileName;

  // Filter source file line by line - find labels
  foreach($FileContent as $Key => $Value)
  {
    // Find label definitions
    if (preg_match(LABEL_PATTERN,$Value,$Matches))
    {
      print "  label @line ".($Key+1)." ".$Matches[1]."\n";
      $LabelList[]=array("Label"=>$Matches[1],"FileName"=>$FileName,"LineNumber"=>($Key+1));
    }
  }

  // Filter source file line by line - find includes
  foreach($FileContent as $Key => $Value)
  {
    // Find .include "filename" lines
    if (preg_match(INCLUDE_PATTERN,$Value,$Matches))
    {
      print "  include @line ".($Key+1)." --> ".$Matches[1]."\n";
      // Remember links
      $Includes[]=$Matches[1];
    }
  }

  // Print delimiter
  print "\n";
  
  // Recurse includes
  if ($Includes)
  {
    foreach($Includes as $Value)
    {
      $Dir=dirname($FileName)."/";
      if ($Dir=="./")
      {
        $Dir="";
      }
      $Error=SourceAsm($SourceDir, $Dir.$Value, $SourceAsmFiles, $LabelList);
      if ($Error!="")
        return $Error;
    }
    unset($Includes);
  }

  // End
  return "";
}


function PrintSourceAsm(&$SourceAsmFiles)
// Prints all procesed ASM files
{
  print "Asm Source File List\n";
  if (count($SourceAsmFiles))
  foreach($SourceAsmFiles as $Key => $Value)
  {
    print "  ".$Value."\n";
  }
  print "\n";
}


function PrintLabels(&$LabelList)
// Prints all found labels
{
  print "Label List\n";
  if (count($LabelList))
  foreach($LabelList as $Key => $Value)
  {
    print "  ".$Value["Label"]." ".$Value["FileName"]." ".$Value["LineNumber"]."\n";
  }
  print "\n";
}


function CreateWordList($SourceDir, &$LabelList, &$WordList)
// Goes through LabelList and looks for word definitions
// Returns Error String
// IN  $LabelList - Found labels
// OUT $WordList  - Word List (ShortLabel, Word, Comment, Label, FileName)
{

  print "Word List\n";
  
  if (count($LabelList))
  foreach ($LabelList as $Value)
  {
    // Take all VE definitions
    if (stristr(substr($Value["Label"],0,3),"VE_"))
    {
      // Prepare Label without VE_
      $ShortLabel=substr($Value["Label"],3);

      // Prepare search pattern
      $Pattern=str_replace("Q",$ShortLabel,WORD_PATTERN);
      #print "Pattern: ".$Pattern." ".$Value["FileName"]."\n";

      // Read source file
      $FileName=$SourceDir.$Value["FileName"];
      $Error=MyReadFileString($FileName, $FileContent);
      if ($Error!="")
        return $Error;
      $FileContent="\n".$FileContent;
      
      // Find label
      if (preg_match($Pattern,$FileContent,$Matches))
      {
        // Coments - remove semiculomn
        $Comment = rtrim(preg_replace("/\\n; ?(.*)/","$1\n",$Matches[1]));

        // Convert .db parameters into string
        $Word="";
        #$Word=$Matches[2]." : ";

        foreach(explode(",",$Matches[2]) as $Val)
        {
          // String element
          preg_match("/^ *\"([^\"]*)(\" *)/",$Val,$Tmp);
          #print "S:".$Tmp[1]."\n";
          if ($Tmp[1]!="")
          {
            $Word.=$Tmp[1];
          }

          // Hexa number
          preg_match("/\\$([0-9A-F]+)/i",$Val,$Tmp);
          #print "H:".$Tmp[1]."\n";
          if ($Tmp[1]!="")
          {
            $Number=hexdec($Tmp[1]);
            if ($Number!=0)
              $Word.=chr($Number);
          }
          
          // Decimal number
          preg_match("/^([0-9]+)/i",$Val,$Tmp);
          #print "D:".$Tmp[1]."\n";
          if ($Tmp[1]!="")
          {
            $Number=$Tmp[1];
            if ($Number!=0)
              $Word.=chr($Number);
          }
          
        }

        // Store label into array
        $WordList[]=array("Word"=>$Word, "ShortLabel"=>$ShortLabel, "Comment"=>$Comment,
                          "Label"=>$Value["Label"] , "FileName"=>$Value["FileName"]
                        );
        print "  ".$Word." = ".$ShortLabel."\n";
        if($Comment) print "    ".preg_replace("/\\n/","\n    ",$Comment)."\n";
      }

      // Sort Words
      array_multisort($WordList);

      // Clean Up
      unset($FileContent);
    }
  }
  print "\n";
  return "";
}


function GenerateWordList($TemplateDir, $SvnInfoFileName, $DestinationDir, &$LabelList, &$WordList)
// Creates HTML pages with Word List
// Returns Error String
// IN  $TemplateDir    - template directory (file WordList.*.html)
// IN  $LabelList      - list of labels (Label, FileName, LineNumber)
// IN  $WordList       - list of words (ShortLabel, Word, Comment, Label, FileName)
// OUT WordList.*.html - create HTML pages (file WordList.*.html)
{
  // Find all templates
  print "Word List in HTML\n";
  if (count(glob($TemplateDir."WordList.*.html")))
  foreach (glob($TemplateDir."WordList.*.html") as $FileName)
  {
    // Process template file
    print "  Temlate $FileName\n";

    // Read template file
    $Error=MyReadFileString($FileName, $FileContent);
    if ($Error!="")
      return $Error;

    // Find <<SvnInfo>>
    if (!preg_match("/( *)(<<SvnInfo>>)/i",$FileContent,$Matches))
    {
      unset($FileContent);
      return "Missing <<SvnInfo>> in template file";
    }
    $Indent=$Matches[1];

    // Read Svn Info file
    $Error=MyReadFileString($SvnInfoFileName, $SvnInfoFile);
    if ($Error!="")
    {
      // We do not have Svn Info File
      $SvnInfoFile="";
    }
    else
    {
      // We have Svn Info File
      $SvnInfoFile=preg_replace("/^((?:URL|Repository|Last)[^:]*):(.*$)|^.*$/im","<tr><td>$1</td><td>$2</td></tr>",$SvnInfoFile);
      $SvnInfoFile=preg_replace("~<tr><td></td><td></td></tr>\n~i","",$SvnInfoFile);
      $SvnInfoFile="<tr><th colspan=\"2\">Subversion Info</th></tr>".$SvnInfoFile;
    }

    // Put Svn Info into HTML template
    $FileContent=str_ireplace("<<SvnInfo>>", $SvnInfoFile, $FileContent);
    #print $FileContent;


    // Find <<WordList>>
    if (!preg_match("/( *)(<<WordList>>)/i",$FileContent,$Matches))
    {
      unset($FileContent);
      return "Missing <<WordList>> in template file";
    }
    $Indent=$Matches[1];
    
    // Create HTML code - table header
    $WordListHTML[]="<table>";
    $WordListHTML[]="  <tr>";
    $WordListHTML[]="    <th>Word</th>";
    $WordListHTML[]="    <th>Label</th>";
    $WordListHTML[]="    <th>Definition</th>";
    $WordListHTML[]="  </tr>";
    
    // Create HTML code - table lines
    if (count($WordList))
    foreach($WordList as $Key => $Value)
    {
      // Prepare (just for readibility)
      $Word=$Value["Word"];
      $Link="<a href=\"".ASMFILES.FileName2HTML($Value["FileName"])."#".$Value["ShortLabel"].
              "\" title=\"".$Value["Label"]."\">".$Value["ShortLabel"]."</a>";
      $Comment=$Value["Comment"];
      // Generate HTML
      $WordListHTML[]="  <tr>";
      $WordListHTML[]="    <td>".htmlspecialchars($Word)."</td>";
      $WordListHTML[]="    <td>".$Link."</td>";
      $WordListHTML[]="    <td>".htmlspecialchars($Comment)."</td>";
      $WordListHTML[]="  </tr>";
    }
    // Create HTML code - table end
    $WordListHTML[]="</table>";
    
    // Indent and Concatenate lines
    foreach($WordListHTML as $Key => $Value)
    {
      if ($Key>0)
        $WordListHTML[$Key]=$Indent.preg_replace("/\n/","<br>",$Value);
    }
    $WordListHTML=implode("\n",$WordListHTML);
    
    // Put it into HTML template
    $FileContent=str_ireplace("<<WordList>>", $WordListHTML, $FileContent);
    #print $FileContent;
    
    // Create Output File
    $Error=MyWriteFile($DestinationDir.basename($FileName), $FileContent);
    if ($Error!="")
      return $Error;

    // Clear memory
    unset($FileContent);
    unset($WordListHTML);
  }

  // Delimiter
  print "\n";
  return "";
}


function GenerateAsmFiles($TemplateDir, $SourceDir, &$SourceAsmFiles, $DestinationDir)
// Cretaes HTML files from all processed ASM files
// Returns Error String
// IN  $TemplateDir    - directory with template files
// IN  $SourceDir      - directory with asm source files
// OUT $SourceAsmFiles -
// IN  $DestinationDir - directory for generated HTML files
{
  // Info
  print "Copy ASM Files\n";

  // Destination directory exists
  $DestinationDir.=ASMFILES;
  if (!is_dir($DestinationDir))
    if (!@mkdir($DestinationDir))
      return "Unable Create Dir ".$DestinationDir;

  // Read template
  $Error=MyReadFileString($TemplateDir."FileAsm.en.html", $Template);
  if ($Error!="")
    return $Error;

  // Copy all source files
  foreach($SourceAsmFiles as $Key => $FileName)
  {
    print "  ".$FileName."\n";

    // Read ASM file
    $Error=MyReadFileString($SourceDir.$FileName, $FileContent);
    if ($Error!="")
      return $Error;

    // Prepare HTML
    $FileContent=htmlspecialchars($FileContent);
    $FileContent="<pre>\n".$FileContent."\n</pre>";

    // Use Template
    $TemplateWork=$Template;
    $TemplateWork=str_ireplace("<<FileName>>",    $FileName,    $TemplateWork);
    $TemplateWork=str_ireplace("<<FileContent>>", $FileContent, $TemplateWork);

    // Write ASM file in HTML
    $Error=MyWriteFile($DestinationDir.FileName2HTML($FileName), $TemplateWork);
    if ($Error!="")
      return $Error;
  }
  
  // Delimiter
  print "\n";
  return "";
}


// **********************************************************************
// Main Block
// **********************************************************************

// Global Like Variables (arrays)
//   $CFG            - Config parameters
//   $SourceAsmFiles - All processed ASM files (filenames)
//   $LabelList      - All label definitions (Label, FileName, LineNumber)
//   $WordList       - Word List (ShortLabel, Word, Comment, Label, FileName)

// Process Command Line Parameters
Error(Parameters($CFG));

// Process all ASM files from the root level
Error(SourceAsm($CFG["SourceDir"], $CFG["SourceAsm"], $SourceAsmFiles, $LabelList));
PrintSourceAsm($SourceAsmFiles);
PrintLabels($LabelList);

// Destilate Labels and Words
Error(CreateWordList($CFG["SourceDir"], $LabelList, $WordList));

// Create HTML WordList
Error(GenerateWordList(
    $CFG["TemplateDir"],
    $CFG["SourceDir"].$CFG["SvnInfoFileName"],
    $CFG["DestinationDir"],
    $LabelList, $WordList));

// Copy ASM files and convert them into HTML
Error(GenerateAsmFiles($CFG["TemplateDir"], $CFG["SourceDir"], $SourceAsmFiles, $CFG["DestinationDir"]));

// Finish
print "O.K.\n";
return

// Zpracování readme autora
// Vyplatilo by se udělat samostatny formatovaci a nahrazovaci mechanizmus
// Zpracování templejtů do samostatného podprogramu (vyřešit indent...)
//    tím se vyřeší i en/cs verze Asm souboru
// Generovat log do souboru místo printu (zvážit) oddělit chyby a varování
// Vyčistit cílový adresář
// Process all FORTH files
// Problém s rekurzí (potenciální nekonečno)
// Chtělo by to do stránek vkládat info o verzi a (c)
?>