JsonCli.psm1


#!/usr/bin/env pwsh
#region Classes

class JsonCli {
  static hidden [hashtable] $parsers

  static JsonCli() {
    [JsonCli]::parsers = [JsonCli]::GetParsers()
  }

  static [string] GetParser([string]$Command) {
    # Extract the base command (everything before the first space)
    $BaseCommand = $Command.Split(' ')[0]

    if ([JsonCli]::parsers.ContainsKey($BaseCommand)) {
      return [JsonCli]::parsers[$BaseCommand]
    }

    # Regex-based fallback for similar commands
    $p = switch -regex ($BaseCommand) {
      "*json*" { "--json"; break }
      "*xml*" { "--xml"; break }
      "*csv*" { "--csv"; break }
      "*log*" { "--syslog"; break }
      "*config*" { "--ssh-conf"; break }
      default {
        Write-Warning "No predefined parser found for '$Command'."
        $null
      }
    }
    return $p
  }
  static [string] ParseGeneric([string[]]$Lines) {
    $ParsedOutput = foreach ($Line in $Lines) {
      [PSCustomObject]@{ RawLine = $Line }
    }
    return $ParsedOutput | ConvertTo-Json -Depth 1
  }
  static [string] RunCommand([string]$Command) {
    $Parser = [JsonCli]::GetParser($Command) -or [JsonCli]::GetFallbackParser($Command)
    # if ($null -eq $Parser) {
    # throw [System.Exception]::new("No parser available for the command: $Command")
    # }

    if ($null -eq $Parser) {
      Write-Warning "No parser available for the command: $Command. Falling back to generic parsing."
      return [JsonCli]::ParseGeneric([JsonCli]::ExecuteCommand($Command))
    }

    try {
      $Output = [scriptblock]::create("$Command").Invoke() | jc $Parser
      return $Output
    } catch {
      Write-Warning "Parser execution failed: $Command"
      return [JsonCli]::ParseGeneric([JsonCli]::ExecuteCommand($Command))
    }
  }
  static [string[]] ExecuteCommand([string]$Command) {
    try {
      return [scriptblock]::create("$Command").Invoke()
    } catch {
      Write-Warning "Failed to execute command: $Command"
      return @()
    }
  }
  static [string] RunFallback([string]$Command) {
    $Output = & $Command
    $Lines = $Output -split "`n"
    return [JsonCli]::ParseGeneric($Lines)
  }
  static [hashtable] GetParsers() {
    $p = @{}
    try {
      # Run 'jc -hhh' to get categorized parsers
      $Output = jc -hhh
      $Lines = $Output -split "`n"

      foreach ($Line in $Lines) {
        # Match parser lines like '--csv CSV file parser'
        if ($Line -match '^(?<Parser>\-\-\S+)\s+(?<Description>.+)$') {
          # Extract the base command (e.g., 'csv' from '--csv')
          $Command = ($Matches['Parser'] -replace '^\-\-', '').Split('-')[0]
          $p[$Command] = $Matches['Parser']
        }
      }

      if ($p.Count -eq 0) {
        Write-Warning "No parsers found. Check if 'jc' is installed and accessible."
      }
    } catch {
      Write-Warning "Failed to fetch parsers from 'jc'. Ensure it's installed and working."
    }
    return $p
  }
  static [void] Log([string]$Message, [string]$LogLevel = "Info") {
    $Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
    Write-Host "[$LogLevel] ${Timestamp}: $Message"
  }
}
#endregion Classes
# Types that will be available to users when they import the module.
$typestoExport = @(
  [JsonCli]
)
$TypeAcceleratorsClass = [PsObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
foreach ($Type in $typestoExport) {
  if ($Type.FullName -in $TypeAcceleratorsClass::Get.Keys) {
    $Message = @(
      "Unable to register type accelerator '$($Type.FullName)'"
      'Accelerator already exists.'
    ) -join ' - '

    [System.Management.Automation.ErrorRecord]::new(
      [System.InvalidOperationException]::new($Message),
      'TypeAcceleratorAlreadyExists',
      [System.Management.Automation.ErrorCategory]::InvalidOperation,
      $Type.FullName
    ) | Write-Warning
  }
}
# Add type accelerators for every exportable type.
foreach ($Type in $typestoExport) {
  $TypeAcceleratorsClass::Add($Type.FullName, $Type)
}
# Remove type accelerators when the module is removed.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
  foreach ($Type in $typestoExport) {
    $TypeAcceleratorsClass::Remove($Type.FullName)
  }
}.GetNewClosure();

$scripts = @();
$Public = Get-ChildItem "$PSScriptRoot/Public" -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue
$scripts += Get-ChildItem "$PSScriptRoot/Private" -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue
$scripts += $Public

foreach ($file in $scripts) {
  Try {
    if ([string]::IsNullOrWhiteSpace($file.fullname)) { continue }
    . "$($file.fullname)"
  } Catch {
    Write-Warning "Failed to import function $($file.BaseName): $_"
    $host.UI.WriteErrorLine($_)
  }
}

$Param = @{
  Function = $Public.BaseName
  Cmdlet   = '*'
  Alias    = '*'
}
Export-ModuleMember @Param