Click here to Skip to main content
15,891,567 members
Articles / Programming Languages / C#

Visual FoxPro Lines of Code Analysis

Rate me:
Please Sign up or sign in to vote.
4.64/5 (5 votes)
5 Jan 2012CPOL2 min read 77K   2.3K   14  
Lines of Code Counter in C# that analyze FoxPro Projects (PJX)
using System;
using System.Data;
using System.Data.OleDb;
using System.IO;
using System.Windows.Forms;

namespace VFPLOCCounter
{
  public partial class MainForm : Form
  {
    private DataTable displayTable = new DataTable();
    private DataTable dt = new DataTable();
    private DataRow dr;
    private DataTable vfpProject = new DataTable();
    private string pjxPath;
    private string pjxName;
    private string pjxPathOnly;

    private float pjxLines = 0;
    private float totalLines = 0, numberOfMethods = 0;
    private float codeLines = 0, commentLines = 0, blankLines = 0;
    private float codePercent = 0, commentPercent = 0, blankPercent = 0;
    private float numberOfErrors = 0;
    private bool fileError = false;

    private float fileTotalLines = 0, fileNumberOfMethods = 0;
    private float fileCodeLines = 0, fileCommentLines = 0, fileBlankLines = 0;
    private float fileCodePercent = 0, fileCommentPercent = 0, fileBlankPercent = 0;


    public MainForm()
    {
      InitializeComponent();
    }
    /// <summary>
    /// Select the folder and .PJX project to analyze.
    /// Also reset all on-screen displays, as well as clearing the display grid.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnSelectFolder_Click(object sender, EventArgs e)
    {
      btnProcess.Enabled = false;
      btnPrintPreview.Enabled = false;
      txtFolder.Text = "";

      DialogResult dialogResult = ofdProject.ShowDialog();
      if (dialogResult == DialogResult.OK)
      {
        txtFolder.Text = ofdProject.FileName;
        btnProcess.Enabled = true;

        totalLines = 0;
        numberOfMethods = 0;
        codeLines = 0;
        commentLines = 0;
        blankLines = 0;
        codePercent = 0;
        commentPercent = 0;
        blankPercent = 0;
        numberOfErrors = 0;

        pbProcess.Value = 0;

        txtNumberOfFiles.Text = "";
        txtTotalLines.Text = "";
        txtNumMethods.Text = "";
        txtBlankLines.Text = "";
        txtCommentLines.Text = "";
        txtCodeLines.Text = "";

        txtCodePercent.Text = "";
        txtCommentPercent.Text = "";
        txtBlankPercent.Text = "";
        lblCurrentFile.Text = "";

        displayTable.Clear();
        dgvResults.DataSource = displayTable;
        Refresh();
      }
    }
    /// <summary>
    /// Start the analysis of the selected .PJX project file
    /// After analysis, calculates the Project totals.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnProcess_Click(object sender, EventArgs e)
    {
      OpenProject(txtFolder.Text);
      BuildDisplayTable();
      ReadProject();

      foreach (DataRow row in displayTable.Rows)
      {
        totalLines += Convert.ToInt32(row["totalLines"]);
        numberOfMethods += Convert.ToInt32(row["numberMethods"]);
        blankLines += Convert.ToInt32(row["blankLines"]);
        commentLines += Convert.ToInt32(row["commentLines"]);
        codeLines += Convert.ToInt32(row["codeLines"]);
      }
      txtNumberOfFiles.Text = pjxLines.ToString();
      txtTotalLines.Text = totalLines.ToString();
      txtNumMethods.Text = numberOfMethods.ToString();
      txtBlankLines.Text = blankLines.ToString();
      txtCommentLines.Text = commentLines.ToString();
      txtCodeLines.Text = codeLines.ToString();
      // Calculate the percentages
      codePercent = (codeLines / totalLines) * 100;
      commentPercent = (commentLines / totalLines) * 100;
      blankPercent = (blankLines / totalLines) * 100;

      txtCodePercent.Text = codePercent.ToString();
      txtCommentPercent.Text = commentPercent.ToString();
      txtBlankPercent.Text = blankPercent.ToString();
      Refresh();
      lblCurrentFile.Text = "";
      MessageBox.Show("Analysis Complete!");
      btnPrintPreview.Enabled = true;
    }
    /// <summary>
    /// Read the open project records from the vfpProject DataTable, call appropriate analyze routines
    /// to perform the analysis.
    /// </summary>
    private void ReadProject()
    {
      pbProcess.Maximum = Convert.ToInt32(pjxLines);
      foreach (DataRow r in vfpProject.Rows)
      {
        pbProcess.Increment(1);
        lblCurrentFile.Text = r["name"].ToString();

        switch (r["type"].ToString())
        {
          case "P":
            ProcessProgram(r["name"].ToString());
            break;
          case "M":
            ProcessMenu(r["name"].ToString());
            break;
          case "V":
            ProcessClassLibrary(r["name"].ToString());
            break;
          case "K":
            ProcessForm(r["name"].ToString());
            break;
          case "T":
            ProcessInclude(r["name"].ToString());
            break;
          case "R":
            ProcessReport(r["name"].ToString());
            break;
        }
      }
    }
    /// <summary>
    /// Analyze .PRG program file.
    /// </summary>
    /// <param name="p">Path and file name of .PRG file to analyze</param>
    private void ProcessProgram(string p)
    {
      string prgPath = pjxPathOnly + "\\" + p;
      string prgName = Path.GetFileName(p);
      try
      {
        ResetFileVariables();

        string[] prgLines = File.ReadAllLines(prgPath);

        fileTotalLines = prgLines.Length;
        foreach (string line in prgLines)
        {
          string vLine = line.Replace("\r\n", "\r");
          vLine = line.Replace("\t", "");
          if (vLine.Length == 0)
            fileBlankLines++;
          else
          {
            fileNumberOfMethods += Occurs("PROCEDURE ", vLine.ToUpper());
            fileNumberOfMethods += Occurs("FUNCTION ", vLine.ToUpper());

            // Check for lines beginning with && as comments (bad form but VFP accepts it)
            // Correctly account for short lines
            if (vLine.Substring(0, 1) == "*" ||
               (vLine.Length > 2 && vLine.Substring(0, 3) == "*!*") ||
               (vLine.Length > 1 && vLine.Substring(0, 2) == "&&"))
              fileCommentLines++;
            else
              fileCodeLines++;
          }
        }
        AddRecordToDisplay(prgName, "Program");
      }
      catch (Exception e)
      {
        AddRecordToDisplay(prgName, "Program");
      }
    }
    /// <summary>
    /// Analyze a Menu (.MNX) file
    /// </summary>
    /// <param name="p">Path and file name of .MNX file to analyze</param>
    private void ProcessMenu(string p)
    {
      string mnxPath = pjxPathOnly + "\\" + p;
      string mnxName = Path.GetFileName(p);

      ResetFileVariables();
      OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
      cb.Provider = "VFPOLEDB";
      cb.DataSource = mnxPath;
      using (OleDbConnection conn = new OleDbConnection(cb.ConnectionString))
      {
        try
        {
          conn.Open();
          dt.Clear();
          dt.Columns.Clear();
          OleDbDataAdapter da = new OleDbDataAdapter("select * from '" + mnxName + "' where objtype = 3", conn);
          try
          {
            da.Fill(dt);
          }
          catch (OleDbException)
          {
            numberOfErrors++;
            fileError = true;
          }
          foreach (DataRow r in dt.Rows)
          {
            string m;
            m = r["procedure"].ToString();
            if (m.Length > 0)
            {
              fileNumberOfMethods += Occurs("PROCEDURE", m);
              m = m.Replace("\r\n", "\r");
              m = m.Replace("\t", "");
              string[] seps = new string[] { "\r" };
              string[] am = m.Split(seps, StringSplitOptions.None);
              foreach (string s in am)
              {
                if (s.Length == 0)
                  fileBlankLines++;
                else
                {
                  // Check for lines beginning with && as comments (bad form but VFP accepts it)
                  // Correctly account for short lines
                  if (s.Substring(0, 1) == "*" ||
                     (s.Length > 2 && s.Substring(0, 3) == "*!*") ||
                     (s.Length > 1 && s.Substring(0, 2) == "&&"))
                    fileCommentLines++;
                  else
                    fileCodeLines++;
                }
              }
            }
          }
          fileTotalLines = fileBlankLines + fileCommentLines + fileCodeLines;
        }
        catch
        {
        }
      }
      AddRecordToDisplay(mnxName, "Menu");
    }
    /// <summary>
    /// Analyze Class Library .VCX file
    /// </summary>
    /// <param name="p">Path and file name of .VCX file to analyze</param>
    private void ProcessClassLibrary(string p)
    {
      string vcxPath = pjxPathOnly + "\\" + p;
      string vcxName = Path.GetFileName(p);

      ResetFileVariables();
      OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
      cb.Provider = "VFPOLEDB";
      cb.DataSource = vcxPath;
      using (OleDbConnection conn = new OleDbConnection(cb.ConnectionString))
      {
        try
        {
          conn.Open();
          dt.Clear();
          dt.Columns.Clear();
          OleDbDataAdapter da = new OleDbDataAdapter("select * from '" + vcxName + "'", conn);
          try
          {
            da.Fill(dt);
          }
          catch (OleDbException)
          {
            numberOfErrors++;
            fileError = true;
          }
          foreach (DataRow r in dt.Rows)
          {
            string m;
            m = r["methods"].ToString();
            if (m.Length > 0)
            {
              fileNumberOfMethods += Occurs("PROCEDURE", m);
              m = m.Replace("\r\n", "\r");
              m = m.Replace("\t", "");
              string[] seps = new string[] { "\r" };
              string[] am = m.Split(seps, StringSplitOptions.None);
              foreach (string s in am)
              {
                if (s.Length == 0)
                  fileBlankLines++;
                else
                {
                  // Check for lines beginning with && as comments (bad form but VFP accepts it)
                  // Correctly account for short lines
                  if (s.Substring(0, 1) == "*" ||
                     (s.Length > 2 && s.Substring(0, 3) == "*!*") ||
                     (s.Length > 1 && s.Substring(0, 2) == "&&"))
                    fileCommentLines++;
                  else
                    fileCodeLines++;
                }
              }
            }
          }
          fileTotalLines = fileBlankLines + fileCommentLines + fileCodeLines;
        }
        catch
        {
        }
      }
      AddRecordToDisplay(vcxName, "Class Library");
    }
    /// <summary>
    /// Analyze a Form (.SCX) file
    /// </summary>
    /// <param name="p">Path and file name of .SCX file to analyze</param>
    private void ProcessForm(string p)
    {
      string scxPath = pjxPathOnly + "\\" + p;
      string scxName = Path.GetFileName(p);

      ResetFileVariables();
      OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
      cb.Provider = "VFPOLEDB";
      cb.DataSource = scxPath;
      using (OleDbConnection conn = new OleDbConnection(cb.ConnectionString))
      {
        try
        {
          conn.Open();
          dt.Clear();
          dt.Columns.Clear();
          OleDbDataAdapter da = new OleDbDataAdapter("select * from '" + scxName + "'", conn);
          try
          {
            da.Fill(dt);
          }
          catch (OleDbException)
          {
            numberOfErrors++;
            fileError = true;
          }
          foreach (DataRow r in dt.Rows)
          {
            string m;
            m = r["methods"].ToString();
            if (m.Length > 0)
            {
              fileNumberOfMethods += Occurs("PROCEDURE", m);
              m = m.Replace("\r\n", "\r");
              m = m.Replace("\t", "");
              string[] seps = new string[] { "\r" };
              string[] am = m.Split(seps, StringSplitOptions.None);
              foreach (string s in am)
              {
                if (s.Length == 0)
                  fileBlankLines++;
                else
                {
                  // Check for lines beginning with && as comments (bad form but VFP accepts it)
                  // Correctly account for short lines
                  if (s.Substring(0, 1) == "*" ||
                     (s.Length > 2 && s.Substring(0, 3) == "*!*") ||
                     (s.Length > 1 && s.Substring(0, 2) == "&&"))
                    fileCommentLines++;
                  else
                    fileCodeLines++;
                }
              }
            }
          }
          fileTotalLines = fileBlankLines + fileCommentLines + fileCodeLines;
        }
        catch
        {
        }
      }
      AddRecordToDisplay(scxName, "Form");
    }
    /// <summary>
    /// Analyze a .H include file
    /// </summary>
    /// <param name="p">Path and file name of the .H file to analyze</param>
    private void ProcessInclude(string p)
    {
      string hPath = pjxPathOnly + "\\" + p;
      string hName = Path.GetFileName(p);
      try
      {
        ResetFileVariables();

        string[] hLines = File.ReadAllLines(hPath);
        
        fileTotalLines = hLines.Length;
        foreach (string vLine in hLines)
        {
          if (vLine.Length == 0)
            fileBlankLines++;
          else
          {
            fileNumberOfMethods += Occurs("PROCEDURE ", vLine.ToUpper());
            fileNumberOfMethods += Occurs("FUNCTION ", vLine.ToUpper());
            // Check for lines beginning with && as comments (bad form but VFP accepts it)
            // Correctly account for short lines
            if (vLine.Substring(0, 1) == "*" ||
               (vLine.Length > 2 && vLine.Substring(0, 3) == "*!*") ||
               (vLine.Length > 1 && vLine.Substring(0, 2) == "&&"))
              fileCommentLines++;
            else
              fileCodeLines++;
          }
        }
        AddRecordToDisplay(hName, "Include");
      }
      catch
      {
        AddRecordToDisplay(hName, "Include");
      }
    }
    /// <summary>
    /// Analyze .FRX report file
    /// </summary>
    /// <param name="p">.FRX file to analyze</param>
    private void ProcessReport(string p)
    {
      string frxPath = pjxPathOnly + "\\" + p;
      string frxName = Path.GetFileName(p);

      ResetFileVariables();
      OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
      cb.Provider = "VFPOLEDB";
      cb.DataSource = frxPath;
      using (OleDbConnection conn = new OleDbConnection(cb.ConnectionString))
      {
        try
        {
          conn.Open();
          dt.Clear();
          dt.Columns.Clear();
          OleDbDataAdapter da = new OleDbDataAdapter("select * from '" + frxName + "' where objtype = 8", conn);
          try
          {
            da.Fill(dt);
          }
          catch (OleDbException)
          {
            numberOfErrors++;
            fileError = true;
          }
          foreach (DataRow r in dt.Rows)
          {
            string m;
            m = r["expr"].ToString();
            if (m.Length > 0)
            {
              fileNumberOfMethods += Occurs("PROCEDURE", m);
              m = m.Replace("\r\n", "\r");
              m = m.Replace("\t", "");
              string[] seps = new string[] { "\r" };
              string[] am = m.Split(seps, StringSplitOptions.None);
              foreach (string s in am)
              {
                if (s.Length == 0)
                  fileBlankLines++;
                else
                {
                  // Check for lines beginning with && as comments (bad form but VFP accepts it)
                  // Correctly account for short lines
                  if (s.Substring(0, 1) == "*" ||
                     (s.Length > 2 && s.Substring(0, 3) == "*!*") ||
                     (s.Length > 1 && s.Substring(0, 2) == "&&"))
                    fileCommentLines++;
                  else
                    fileCodeLines++;
                }
              }
            }
            // Added for VFP 9 reports 8/31/2011
            m = r["supexpr"].ToString();
            if (m.Length > 0)
            {
              fileNumberOfMethods += Occurs("PROCEDURE", m);
              m = m.Replace("\r\n", "\r");
              m = m.Replace("\t", "");
              string[] seps = new string[] { "\r" };
              string[] am = m.Split(seps, StringSplitOptions.None);
              foreach (string s in am)
              {
                if (s.Length == 0)
                  fileBlankLines++;
                else
                {
                  // Check for lines beginning with && as comments (bad form but VFP accepts it)
                  if (s.Substring(0, 1) == "*" || s.Substring(0, 3) == "*!*" || s.Substring(0, 2) == "&&")
                    fileCommentLines++;
                  else
                    fileCodeLines++;
                }
              }
            }
          }
          fileTotalLines = fileBlankLines + fileCommentLines + fileCodeLines;
        }
        catch
        {
        }
      }
      AddRecordToDisplay(frxName, "Report");
    }
    /// <summary>
    /// Open a VFP .PJX Project file, load a DataTable with the PJX contents
    /// </summary>
    /// <param name="theProject">Path and file name of the .PJX file to open</param>
    private void OpenProject(string theProject)
    {
      pjxPath = Path.GetFullPath(theProject);
      pjxName = Path.GetFileName(theProject);
      pjxPathOnly = Path.GetDirectoryName(theProject);

      OleDbConnectionStringBuilder cb = new OleDbConnectionStringBuilder();
      cb.Provider = "VFPOLEDB";
      cb.DataSource = pjxPath;
      using (OleDbConnection conn = new OleDbConnection(cb.ConnectionString))
      {
        conn.Open();
        vfpProject.Clear();
        vfpProject.Clear();
        // 12/30/2011 - Add 'T' to select statement to pull Include records
        OleDbDataAdapter da = new OleDbDataAdapter(
          "select name, type from '" + pjxName +
          "' where inlist(type, 'P', 'M', 'V', 'T', 'K', 'R') order by type", conn);
        try
        {
          da.Fill(vfpProject);
          pjxLines = vfpProject.Rows.Count;
        }
        catch (OleDbException ode)
        {
          MessageBox.Show("Error opening Project:\n\n" +
                          ode.Message);
        }
      }
    }
    /// <summary>
    /// Add a record to the displayResults DataTable and refresh the grid
    /// </summary>
    /// <param name="fileName">Name of file that was analyzed</param>
    /// <param name="fileType">Type of the file, ie, report, form, menu, etc.</param>
    private void AddRecordToDisplay(string fileName, string fileType)
    {
      CalculateFilePercentages();
      dr = displayTable.NewRow();
      dr["fileName"] = fileName;
      dr["fileType"] = fileType;
      if (fileError)
        dr["fileName"] += " error";
      dr["totalLines"] = fileTotalLines.ToString().PadLeft(5);
      dr["numberMethods"] = fileNumberOfMethods.ToString().PadLeft(5);
      dr["codeLines"] = fileCodeLines.ToString().PadLeft(5);
      dr["commentLines"] = fileCommentLines.ToString().PadLeft(5);
      dr["blankLines"] = fileBlankLines.ToString().PadLeft(5);
      dr["codePercent"] = fileCodePercent.ToString("N").PadLeft(10);
      dr["commentPercent"] = fileCommentPercent.ToString("N").PadLeft(10);
      dr["blankPercent"] = fileBlankPercent.ToString("N").PadLeft(10);
      displayTable.Rows.Add(dr);
      dgvResults.DataSource = displayTable;
      Refresh();
    }
    /// <summary>
    /// Calculates the percentages for the current file
    /// </summary>
    private void CalculateFilePercentages()
    {
      if (fileTotalLines > 0)
      {
        fileCodePercent = (fileCodeLines / fileTotalLines) * 100;
        fileCommentPercent = (fileCommentLines / fileTotalLines) * 100;
        fileBlankPercent = (fileBlankLines / fileTotalLines) * 100;
      }
    }
    /// <summary>
    /// Resets File Variables to zero
    /// </summary>
    private void ResetFileVariables()
    {
      fileTotalLines = 0;
      fileNumberOfMethods = 0;
      fileCodeLines = 0;
      fileCommentLines = 0;
      fileBlankLines = 0;
      fileBlankPercent = 0;
      fileCodePercent = 0;
      fileCommentPercent = 0;
    }
    /// <summary>
    /// Define the schema of the DataTable tied to the display grid.
    /// </summary>
    private void BuildDisplayTable()
    {
      displayTable.Clear();
      displayTable.Columns.Clear();

      displayTable.Columns.Add(new DataColumn("fileName", typeof(string)));
      displayTable.Columns.Add(new DataColumn("fileType", typeof(string)));

      displayTable.Columns.Add(new DataColumn("totalLines", typeof(string)));
      displayTable.Columns.Add(new DataColumn("numberMethods", typeof(string)));

      displayTable.Columns.Add(new DataColumn("codeLines", typeof(string)));
      displayTable.Columns.Add(new DataColumn("commentLines", typeof(string)));
      displayTable.Columns.Add(new DataColumn("blankLines", typeof(string)));

      displayTable.Columns.Add(new DataColumn("codePercent", typeof(string)));
      displayTable.Columns.Add(new DataColumn("commentPercent", typeof(string)));
      displayTable.Columns.Add(new DataColumn("blankPercent", typeof(string)));

      // Set the captions for the columns in the table
      dgvResults.Columns[0].HeaderText = "File Name";
      dgvResults.Columns[1].HeaderText = "File Type";

      dgvResults.Columns[2].HeaderText = "# Lines";
      dgvResults.Columns[3].HeaderText = "# Methods";

      dgvResults.Columns[4].HeaderText = "# Lines Code";
      dgvResults.Columns[5].HeaderText = "# Comment Lines";
      dgvResults.Columns[6].HeaderText = "# Blank Lines";

      dgvResults.Columns[7].HeaderText = "% Code";
      dgvResults.Columns[8].HeaderText = "% Comments";
      dgvResults.Columns[9].HeaderText = "% Blank Lines";
    }
    /// <summary>
    /// Counts number of occurrances of <code>cExpression</code> in <code>cString</code>
    /// </summary>
    /// <param name="cString">String to search within</param>
    /// <param name="cExpression">String to look for</param>
    /// <returns></returns>
    public static int Occurs(string cString, string cExpression)
    {
      int nPos = 0;
      int nOccured = 0;
      do
      {
        //Look for the search string in the expression
        nPos = cExpression.IndexOf(cString, nPos);
        if (nPos < 0)
        {
          //This means that we did not find the item			
          break;
        }
        else
        {
          //Increment the occured counter based on the current mode we are in			
          nOccured++;
          nPos++;
        }
      } while (true);
      //Return the number of occurences	
      return nOccured;
    }
    /// <summary>
    /// Displays the results of the project analysis in a Report Preview form
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnPrintPreview_Click(object sender, EventArgs e)
    {
      ReportForm rf = new ReportForm();
      displayTable.Columns.Add(new DataColumn("projectName", typeof(string)));
      displayTable.Columns.Add(new DataColumn("projectPath", typeof(string)));
      foreach (DataRow r in displayTable.Rows)
      {
        r["projectName"] = pjxName;
        r["projectPath"] = pjxPath;
      }


      rf.ReportTable = displayTable.Copy();
      rf.ShowDialog();
      rf.Dispose();

      displayTable.Columns.Remove("projectPath");
      displayTable.Columns.Remove("projectName");
    }
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I develop software for a leading healthcare system in Northern Illinois.

Comments and Discussions