1
\$\begingroup\$

I'm often frustrated by the lack of ability to obtain simple size metrics about code projects I'm working on, like total lines of code, or character count. To fix this, I wrote the following PowerShell utility:

Function GetProjectMetrics
{
    [CmdletBinding()]
    Param(
        [Parameter(mandatory=$True)]
        [string]$projectPath,

        [Parameter(mandatory=$True)]
        [string]$fileFilter,

        [switch]$ignorews
    )

    Set-Location $projectPath
    Switch($ignorews)
    {
        $True 
        { 
            Get-ChildItem -recurse -include $fileFilter                   `
                | Get-Content                                             `
                | Measure-Object -Character -Word -Line -IgnoreWhiteSpace
            Break
        }

        $False
        {
            Get-ChildItem -recurse -include $fileFilter `
                | Get-Content                           `
                | Measure-Object -Character -Word -Line
            Break
        }

        Default
        {
            Write-Host "Error executing 'GetProjectMetrics'."
            Break
        }
    }
}

New-Alias -Name gpm -Value GetProjectMetrics
New-Alias -Name getpm -Value GetProjectMetrics

The above code is placed within my Microsoft.PowerShell_profile.ps1 file (my PowerShell profile), which gives me the ability to use it straight from the PowerShell command line.

Here's some example usage:

PS C:\Users\Ethan> gpm ".\Documents\MyPythonProject" "*.py" -ignorews
PS C:\Users\Ethan> getpm ".\Documents\MyPythonProject" "*.py"
PS C:\Users\Ethan> GetProjectMetrics ".\OneDrive\CS Projects\CS Tests" "*.cs" -ignorews

What can I improve?

\$\endgroup\$
2
\$\begingroup\$

My suggestions:

The script (rewritten almost completely):

<#
 SYNOPSIS: Calculates simple size metrics about code projects.
          Combination of `Get-ChildItem`, `Get-Content` and `Measure-Object`

  Parameter         Meaning (see native cmdlet)
  ---------         ---------------------------
  -Path             obvious (Convert-Path, Get-ChildItem, Get-Item, Push-Location)
  -LiteralPath      obvious (Convert-Path, Get-ChildItem, Get-Item, Push-Location)
  -Filter           obvious (Get-ChildItem)
  -IgnoreWhiteSpace obvious (Measure-Object)
  -Detailed         returns size metrics about individual files severally
  -PushLocation     side effect: `Push-Location`

 RETURNS: Custom combination of GenericMeasureInfo and TextMeasureInfo:

  Name       Type     Meaning
  ----       ----     -------
  Count      [int]    number of files of given pattern in given (literal)path
  Lines      [int]    number of lines
  Words      [int]    number of words
  Characters [int]    number of characters
  Property   [string] tested path
#>

Set-StrictMode -Version latest

Function Measure-Project
{
    [CmdletBinding(DefaultParameterSetName='Items')]
    Param(
        [Alias('projectPath')]
        [Parameter(Mandatory=$false,
                   Position=0,
                   ParameterSetName='Items')]
        [string]$Path='.\',

        [Parameter(Mandatory=$false,
                   Position=0,
                   ParameterSetName='LiteralItems')]
        [string]$LiteralPath='.\',

        [Alias('fileFilter')]
        [Parameter(Mandatory=$false,
                   Position=1)]
        [string]$Filter='*.ps1',

        [Alias('IgnoreWS')]
        [switch]$IgnoreWhiteSpace,

        [Alias('PushD')]
        [switch]$PushLocation,

        [switch]$Detailed
    )
    Function out ($Cnt, $Prm, $Obj) {
        $auxCounter = $Obj | Measure-Object -Character -Word -Line @auxParMO
        $auxCounter | Add-Member -NotePropertyName Count -NotePropertyValue $Cnt
        $auxCounter.Property = $Prm
        return $auxCounter
    }
    if ( $PSCmdlet.ParameterSetName -eq 'LiteralItems') {
        $auxParTP = @{ LiteralPath = $LiteralPath }
    } else {
        $auxParTP = @{ Path = $Path }
    }
    if ( Test-Path @auxParTP ) {
        $auxPath = Convert-Path @auxParTP
        if ( (Get-Item @auxParTP) -is [System.IO.DirectoryInfo]) {
            $auxFiles = Get-ChildItem @auxParTP -Filter $Filter -Recurse -File
            $auxParMO = @{ IgnoreWhiteSpace = $IgnoreWhiteSpace.IsPresent } 
                         # $PSBoundParameters.ContainsKey('IgnoreWhiteSpace')
            if ($Detailed.IsPresent -and $auxFiles) {
                $auxFiles | ForEach-Object {
                    out -Cnt 1 -Prm $_.FullName -Obj ($_ | Get-Content)
                }
            } else {
                $auxPar = Join-Path -Path $auxPath -ChildPath $Filter
                if ( $auxFiles ) {
                    if ( $auxFiles -is [array] ) {
                        $auxCnt = $auxFiles.Count
                    } else {
                        $auxCnt = 1
                    }
                    out -Cnt $auxCnt -Prm $auxPar -Obj ($auxFiles | Get-Content)
                } else {
                    Write-Warning "No '$Filter' inside '$Path'"
                    out -Cnt 0 -Prm $auxPar -Obj ''
                }
            }
            if ( $PushLocation.IsPresent ) {Push-Location @auxParTP}
        } else {
            $auxMsg = "'$($auxParTP.Values.GetEnumerator())' is not a directory"
            Write-Error -Message $auxMsg -Category InvalidArgument
        }
    }
    else
    {
        $auxMsg = "Cannot find path '$($auxParTP.Values.GetEnumerator())'"
        Write-Error -Message $auxMsg -Category ObjectNotFound
    }
}

Sample output demonstrates necessity of -Encoding parameter as both files contain nearly the same text so the utility should show the same count of characters. Of course, I'd expect the same encoding of source files in a particular project so sorry for the following simplified example:

PS D:\PSh> Measure-Project -LiteralPath 'D:\bat\[source]\XX\' -filter * -Detailed | ft

Count Lines Words Characters Property                           
----- ----- ----- ---------- --------                           
    1     1     5        118 D:\bat\[source]\XX\[sourcefile].log
    1     1     5         59 D:\bat\[source]\XX\[sourcefile].txt

PS D:\PSh> type -LiteralPath 'D:\bat\[source]\XX\[sourcefile].txt'
Toto je obsah souboru `D:\bat\[source]\XX\[sourcefile].txt`

PS D:\PSh> type -LiteralPath 'D:\bat\[source]\XX\[sourcefile].log' -Encoding Unicode
Toto je obsah souboru `D:\bat\[source]\XX\[sourcefile].log`
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.