Последнее время размещаю свои модули PowerShell на github. И возникло разумное желание сопроводить модули readme файлом. github поддерживает массу различных вариантов разметки, я выбрал наиболее простой на мой взгляд – markDown (он же поддерживается и другими публичными репозиториями, поэтому на нём и остановился).

Однако, перспектива написания readme файла для каждого модуля “руками”, а потом ещё и корректировка этих описания просто отпугнула. Решил нарисовать скрипт для создания readme.md по комментариям в модуле и описанию модуля.


Модуль генерации readme – ITG.Readme

Приведу код функции, в которой и реализовал требуемый функционал:

function Get-Readme {
    <#
        .Synopsis
            Генерирует readme файл с md разметкой по данным модуля и комментариям к его функциям. 
            Файл предназначен, в частности, для размещения в репозиториях github.
        .Notes
            To-Do:
            - ошибка: генерирует заголовок примера, даже если примеров нет.
            - автоматический поиск и генерацию ссылок по переданному словарю
            - генерация ссылок по наименованиям других функций модуля, и других модулей, если таковые указаны
            - автоматическое выделение url и формирование синтаксиса ссылки в разделах Link
            - ввести поддержку генерации файла для внешних скриптов (именно - с генерацией файла)
            - а также для прочих членов модуля
            - about_commonparameters и другие аналогичные так же в ссылки преобразовывать
        .Inputs
            Через конвейер функция принимает описатели модулей, функций, скриптов. Именно для них и будет сгенерирован readme.md. 
            Получены описатели могут быть через Get-Module, Get-Command и так далее.
        .Outputs
            String. Содержимое readme.md.
        .Link
            [MarkDown (md) Syntax](http://daringfireball.net/projects/markdown/syntax)
        .Link
            [about_comment_based_help](http://technet.microsoft.com/ru-ru/library/dd819489.aspx)
        .Link
            [Написание справки для командлетов](http://go.microsoft.com/fwlink/?LinkID=123415)
        .Example
            Get-Module 'ITG.Yandex.DnsServer' | Get-Readme | Out-File -Path 'readme.md' -Encoding 'UTF8' -Width 1024;
            Генерация readme.md файла для модуля `ITG.Yandex.DnsServer` 
            в текущем каталоге.
        .Example
            Get-Module 'ITG.Yandex.DnsServer' | Get-Readme -OutDefaultFile;
            Генерация readme.md файла для модуля `ITG.Yandex.DnsServer` 
            в каталоге модуля.
    #>
    
    [CmdletBinding(
        DefaultParametersetName='ModuleInfo'
    )]

    param (
        # Описатель модуля
        [Parameter(
            Mandatory=$true
            , Position=0
            , ValueFromPipeline=$true
            , ParameterSetName='ModuleInfo'
        )]
        [PSModuleInfo]
        [Alias('Module')]
        $ModuleInfo
    ,
        [Parameter(
            ParameterSetName='ModuleInfo'
        )]
        [switch]
        $OutDefaultFile
    ,
        # Описатель внешнего сценария
        [Parameter(
            Mandatory=$true
            , Position=0
            , ValueFromPipeline=$true
            , ParameterSetName='ExternalScriptInfo'
        )]
        [System.Management.Automation.ExternalScriptInfo]
        $ExternalScriptInfo
    ,
        # Описатель внешнего сценария
        [Parameter(
            Mandatory=$true
            , Position=0
            , ValueFromPipeline=$true
            , ParameterSetName='FunctionInfo'
        )]
        [System.Management.Automation.FunctionInfo]
        $FunctionInfo
    ,
        [switch]
        [Alias('Short')]
        $ShortDescription
    )

    process {
        switch ( $PsCmdlet.ParameterSetName ) {
            'ModuleInfo' {
                $Funcs = @( `
                    $ModuleInfo.ExportedFunctions.Values `
                    | Sort-Object -Property `
                        @{ Expression={ ( $_.Name -split '-' )[1] } } `
                        , @{ Expression={ ( $_.Name -split '-' )[0] } } `
                );
                $ReadMeContent = & { `
@"
$($ModuleInfo.Name)
$($ModuleInfo.Name -replace '.','=')

$($ModuleInfo.Description)

Версия модуля: **$( $ModuleInfo.Version.ToString() )**
"@
                    $Funcs `
                    | Group-Object -Property `
                        @{ Expression={ ( $_.Name -split '-' )[1] } } `
                    | % -Begin {
@"

Функции модуля
--------------
"@
                    } `
                    -Process {
@"
            
### $($_.Name)
"@
                        $_.Group `
                        | Get-Readme -ShortDescription `
                        ;
                    };

                    if ( -not $ShortDescription ) {
@"

Подробное описание функций модуля
---------------------------------
"@
                        $Funcs `
                        | Get-Readme `
                        ;
                    };
                };
                if ( $OutDefaultFile ) {
                    $ReadMeContent `
                    | Out-File `
                        -FilePath ( Join-Path `
                            -Path ( Split-Path -Path ( $ModuleInfo.Path ) -Parent ) `
                            -ChildPath 'readme.md' `
                        ) `
                        -Force `
                        -Encoding 'UTF8' `
                        -Width 1024 `
                    ;
                } else {
                    return $ReadMeContent;
                };
            }
            'ExternalScriptInfo' {
            }
            'FunctionInfo' {
                $Help = ( $FunctionInfo | Get-Help -Full );
                $Syntax = (
                    $Help.Syntax.SyntaxItem `
                    | % {
                        ,$_.Name `
                        + ( 
                            $_.Parameter `
                            | % {
                                #MamlCommandHelpInfo#parameter
                                $name="-$($_.Name)";
                                if ( $_.position -ne 'named' ) {
                                    $name="[$name]";
                                };
                                if ( $_.parameterValue ) {
                                    $param = "$name <$($_.parameterValue)>";
                                } else {
                                    $param = "$name";
                                };
                                if ( $_.required -ne 'true' ) {
                                    $param = "[$param]";
                                };
                                $param;
                            }
                        ) `
                        + ( & {
                            if ( $FunctionInfo.CmdletBinding ) { '<CommonParameters>' }
                        } ) `
                        -join ' '
                    }
                );
@"
            
#### $($FunctionInfo.Name)

"@
                if ( $ShortDescription ) {
                    $Help.Synopsis;
                    $Syntax `
                    | % {
@"
    
    $_
"@
                    };
                } else {
                    if ( $Help.Description ) {
                        $Help.Description;
                    } else {
                        $Help.Synopsis;
                    };
@"

##### Синтаксис
"@
                    $Syntax `
                    | % {
@"
    
    $_
"@
                    };
                    if ( $Help.Component ) {
@"

##### Компонент

$($Help.Component)
"@
                    };
                    if ( $Help.Functionality ) {
@"

##### Функциональность

Описываемая функция предоставляет следующую функциональность: $($Help.Functionality).
"@
                    };
                    if ( $Help.Role ) {
@"

##### Требуемая роль пользователя

Для выполнения функции $($FunctionInfo.Name) требуется роль $($Help.Component) для учётной записи,
от имени которой будет выполнена описываемая функция.
"@
                    };

                    if ( $Help.Inputs ) {
@"

##### Принимаемые данные по конвейеру

$($Help.Inputs)
"@
                    };
                    if ( $Help.Outputs ) {
@"

##### Передаваемые по конвейеру данные

$($Help.Outputs)
"@
                    };
                    
                    if ( $Help.Parameters.parameter.Count ) {
                        $ParamsDescription = `
                            ( $Help.Parameters | Out-String ) `
                            -replace '<CommonParameters>', '-<CommonParameters>' `
                            -replace '(?m)^\p{Z}{4}-(.+)?\s*?$', '- `$1`' `
                        ;
@"

##### Параметры    
$ParamsDescription
"@
                    };
                    
                    if ( ( @( $Help.examples ) ).count ) {
                        $Help.Examples.example `
                        | % -Begin {
                            $ExNum=0;
@"

##### Примеры использования    
"@
                        } `
                        -Process {
                            ++$ExNum;
                            $Comment = (
                                ( 
                                    $_.remarks `
                                    | % { $_.Text } 
                                ) -join ' ' 
                            ).Trim( ' ', (("`t").Normalize()) );
                            if ( $Comment ) {
@"

$ExNum. $Comment
"@
                            } else {
@"

$ExNum. Пример $ExNum.
"@
                            };
@"

        $($_.code)
"@
                        };
                    };

                    $links = `
                        $Help.relatedLinks.navigationLink `
                        | ? { $_.LinkText } `
                    ;
                    if ( $links ) {
@"

##### Связанные ссылки

"@
                        $links `
                        | % {
@"
- $($_.LinkText)
"@
                        };
                    };
                };
            };
        };
    }
}

Export-ModuleMember `
    Get-Readme `
;

Да, так вот смешно её и назвал – Get-Readme. Данная функция на вход (через конвейер) может принимать описатели модулей, функций, внешних скриптов (ну не придумал я иного перевода к external script). Правда, пока для external script она ничего генерировать не будет, ещё не дописал, а для модулей и функций – вполне работоспособна. Ну и в случае генерации readme для модуля поддерживает ещё один параметр – OutDefaultFile (флаг). При установке этого флага readme не будет отправлен по конвейеру, а будет записан в файл readme.md в каталоге модуля, описатель которого был передан функции Get-Readme.

Пример использования

Import-Module -Path 'ITG.Yandex'     -PassThru `
| Get-Readme -OutDefaultFile;

Эти несколько строк загрузят модуль ITG.Yandex, и будет создан файл readme.md в каталоге модуля ITG.Yandex.

Что же мы получим в итоге? На примере модуля ITG.Yandex сам текст readme.md можно подсмотреть на github, а результат генерации html по markdown размере этого файла – можно увидеть здесь. Согласитесь, с таким readme куда проще знакомиться с модулем, чем вовсе без readme.

Как это работает?

Для генерации описания я использовал механизмы powershell. В частности – возможность генерации описания  специальным образом оформленных комментариев. А описание и версию самого модуля так же получаю через механизмы powerShell. Для того, чтобы снабдить Ваш модуль необходимой информацией, необходимо создать к нему файл манифеста модуля. Для модуля ITG.Readme файл манифеста ITG.Readme.psd1 выглядит следующим образом:

#
# Манифест модуля для модуля "ITG.Readme".
#
# Создано: Sergey S. Betke
#
# Дата создания: 13.11.2012
#
# Архив проекта: https://github.com/sergey-s-betke/ITG.Readme
#

@{

# Файл модуля скрипта или двоичного модуля, связанный с данным манифестом
ModuleToProcess = 'ITG.Readme.psm1'

# Номер версии данного модуля.
ModuleVersion = '1.3.6'

# Уникальный идентификатор данного модуля
GUID = '826e836c-d10c-4d4d-b86b-8b4a41829b00'

# Автор данного модуля
Author = 'Sergey S. Betke'

# Компания, создавшая данный модуль, или его поставщик
CompanyName = 'IT-Service.Nov.RU'

# Заявление об авторских правах на модуль
Copyright = '(c) 2012 Sergey S. Betke. All rights reserved.'

# Описание функций данного модуля
Description = 'Набор функций для PowerShell для генерации readme файла для модулей и функций.'

# Минимальный номер версии обработчика Windows PowerShell, необходимой для работы данного модуля
PowerShellVersion = '2.0'

# Имя узла Windows PowerShell, необходимого для работы данного модуля
PowerShellHostName = ''

# Минимальный номер версии узла Windows PowerShell, необходимой для работы данного модуля
PowerShellHostVersion = ''

# Минимальный номер версии компонента .NET Framework, необходимой для данного модуля
DotNetFrameworkVersion = '2.0'

# Минимальный номер версии среды CLR (общеязыковой среды выполнения), необходимой для работы данного модуля
CLRVersion = '2.0'

# Архитектура процессора (нет, X86, AMD64, IA64), необходимая для работы модуля
ProcessorArchitecture = ''

# Модули, которые необходимо импортировать в глобальную среду перед импортированием данного модуля
RequiredModules = @(
)

# Сборки, которые должны быть загружены перед импортированием данного модуля
RequiredAssemblies = @()

# Файлы скрипта (.ps1), которые запускаются в среде вызывающей стороны перед импортированием данного модуля
ScriptsToProcess = @(
)

# Файлы типа (.ps1xml), которые загружаются при импорте данного модуля
TypesToProcess = @()

# Файлы формата (PS1XML-файлы), которые загружаются при импорте данного модуля
FormatsToProcess = @()

# Модули для импортирования в модуль, указанный в параметре ModuleToProcess, в качестве вложенных модулей
NestedModules = @()

# Функции для экспорта из данного модуля
FunctionsToExport = '*'

# Командлеты для экспорта из данного модуля
CmdletsToExport = '*'

# Переменные для экспорта из данного модуля
VariablesToExport = '*'

# Псевдонимы для экспорта из данного модуля
AliasesToExport = '*'

# Список всех модулей, входящих в пакет данного модуля
ModuleList = @()

# Список всех файлов, входящих в пакет данного модуля
FileList = `
    'ITG.Readme.psm1'

# Личные данные, передаваемые в модуль, указанный в параметре ModuleToProcess
PrivateData = ''

}

Описание модуля я беру из параметра Description (его значение вполне может быть многострочным, не скупитесь на подробности), версию – из ModuleVersion. Что же касается описания функций – для этого и существуют специально подготовленные комментарии. Пример таких комментариев как раз и представлен в заголовке функции Get-Readme в начале статьи. Подобные комментарии, как Вы понимаете, позволяют не только readme генерировать. В первую очередь они позволят Вам использовать следующий код:

Get-Help Get-Readme -Details;

Другими словами, настоятельно рекомендую писать “правильные” комментарии к собственным функциям. Массу приятных бонусов в результате можно получить, в том числе – и readme PowerShell: скриптуем создание readme файла с markdown разметкой для модулей PowerShell.

Где это забрать?

Репозиторий проекта ITG.Readme размещён на github. Пользуйтесь на здоровье PowerShell: скриптуем создание readme файла с markdown разметкой для модулей PowerShell.

Отзывы » (1)

  1. Ещё один репозиторий на github открыл — http://github.com/IT-Service/ITG.Readme. Уже версия 1.5.1. Все пожелания к модулю лучше писать в багтрекер — http://github.com/it-service/ITG.Readme/issues.

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка