﻿# function: Extract-PluginMeta
# x-poshcode-id: 6832
# x-archived: 2017-04-07T08:11:23
# x-published: 2017-04-04T18:22:00
#
# encoding: utf-8
# api: ps
# type: functions
# title: PMD extraction
# description: Provides a crude implementation of Plugin Meta Data extraction
# version: 0.3
# depends: powershell >= 2.0, regex
# category: io
# author: mario
# src: https://pypi.python.org/pypi/pluginconf/0.6.7
# license: Public domain
# config: -
# status: alpha
# priority: extra
# 
# A little consistency always goes a long way. -- This sample comment is a basic
# version of PMD. The format is (1) fairly obvious, (2) easy to extract, (3) most
# compatible to implementations in other scripting languages (Python, Ruby, PHP, 
)
#
# The advantage over hodgepodge comments becomes more striking the more scripts
# amass. Or once integration or packaging become an issue.
#
# This version uses a few crude regex methods to fetch fields and config options:
#
#  · Extract-PluginMeta reads from a given file name.
#  · Simply scans the topmost comment block.
#  · Fields are basically YAML entries.
#  · Key names are case-insensitive, meant to be message/rfc-style,
#    but otherwise free-form and per-project customizable.
#  · Wheres the config: field is a JSON-style list.
#  · No dependency management or anything else provided here.
#
# The default field names are api:, type:, title:, description:, version:, category:
# and config:. Though they're mostly decoration, and it's encouraged to introduce
# new ones, depending on project and tool requirements.


#-- baseline plugin meta data support
function Extract-PluginMeta() {
    <#
      .SYNOPSIS
         Extract plugin meta data from given filename
      .DESCRIPTION
         Reads specified file, returns hashtable for key:value entries
         from topmost comment block. Prepares config{} dicts.
      .PARAMETER fn
         Script to read from
      .OUTPUTS
         Returns a HashTable of extracted field &#8594; values
      .EXAMPLE
         For instance to read a couple of "plugins" at once:
           $menu = (Get-Item tools/*.ps1 | % { Extract-PluginMeta $_ })
         Which builds a usable hashtable list from asorted scripts,
         once they have been documented. Which thus eases processing:
           Show-MenuCLI ($menu | % { $_.title -and $_.type -eq "inline" })
         Or executing "init" plugins for instance:
           $menu | ? { $_.api -eq "mytool" -and $_.type -eq "init" } | { . $_.fn }
      .TEST
         (Extract-PluginMeta $MyInvocation.MyCommand.Definition).version -eq "0.3"
      .NOTES
         Packs comment remainder as `doc` field.
    #>
    Param($fn, $meta=@{}, $cfg=@())

    # read file
    $str = Get-Content $fn | Out-String

    # look for first comment block
    if ($m = [regex]::match($str, '(?m)((?:^\s*[#]+.*$\n?)+)')) {

        # remove leading #&#9251; from lines, then split remainder comment
        $str = $m.groups[1] -replace "(?m)^\s*#[ \t]*", ""
        $str, $doc = [regex]::split($str, '(?m)^$\n')

        # find all `key:value` pairs
        preg_match_all -rx "(?m)^([\w-]+):\s*(.*(?:[\r\n]*^\s+.*$)*)" -str $str | % { $meta[$_[1]] = $_[2].trim() }

        # split out config: and crude-parse it (JSONish serialization)
        preg_match_all -rx "\{(.+?)\}" -str $meta.config | % { $r = @{};
            preg_match_all -rx "([\w]+)\s*[:=]\s*(?:[']?([^,;}]+)[']?)" -str $_[1] | % {  $r[$_[1]] = $_[2] }; $cfg += $r;
        }

        # merge into hashtable
        $meta.fn = $fn
        $meta.doc = ($doc -join "`r`n")
        $meta.config = $cfg
    }
    return $meta  # or return as (New-Object PSCustomObject -Prop $meta)
}

#-- Regex/Select-String -Allmatches as nested array / a convenience shortcut /  well, why, yes; it's a PHP-style function
function preg_match_all() {
    Param($rx, $str)
    $str | Select-String $rx -AllMatches | % { $_.Matches } | % { ,($_.Groups | % { $_.Value }) }
}
