LoginSignup
7
2

More than 3 years have passed since last update.

Github の Wiki を VBA ソースから生成する

Last updated at Posted at 2019-08-03

経緯

VBAのライブラリを作成したので、マニュアルを書きたいが、クラスが多くて大変。自動的に作成したい。

  • JavaDoc みたいにソースの看板に記述したものが GitHub の Wiki に自動的になったらいいな。
  • GitHub の Wiki 向けに作成するのであれば MarkDown が良い。
  • Wiki のサイドバー部分も一緒に生成し、リンクを貼りたい。

適当にググってみたがツールはなさそうなので作ってみよう。

VBA の看板を考える

VBAの看板に @なにがし とか HTML/XML のタグを入れてもいいけど、MarkDown 自体簡略した書き方だし、HTMLやXMLからMarkDown を生成するのは論外だということで、直接 MarkDown を書くことにする。

コメントに書くということと、MarkDown であることが判別する必要があるため、MarkDown の場合には、行頭に「'>」をつけることにする。
段落内の改行は行末尾に半角スペース2個だが、VBEが勝手に消してしまうので仕方なく<BR>を書くことにする。

看板イメージ

VBAソースにMarkDownを記述する。

'>---
'>#### FileExists(FileIO)
'>
'>**Syntax**
'>
'>```
'>flg = FileIO.FileExists(strFile)
'>```
'>
'>**Parameters**
'>
'>|Name|Required/Optional|Data type|Description|
'>---|---|---|---
'>|strFile|必須|String|存在チェックを行うファイルを指定|
'>
'>**Return Value**
'>
'>|Name|Data type|Description|
'>---|---|---
'>|flg|Boolean|存在する場合:True / 存在しない場合:False|
'>
'>
'>**Remarks**
'>
'>ファイル存在チェック<br>
'>指定されたファイルの存在チェックを行う
'>
'>**Example**
'>
'>* None
'>
'>**See also**
'>
'>* FileIO.FolderExists
'>
Public Function FileExists(ByVal strFile As String) As Boolean

    With FSO
        FileExists = .FileExists(strFile)
    End With

End Function

生成後のイメージは以下の通り。Microsoft のマニュアルページを参考にした。


2.3.15.1 FileExists(FileIO)

Syntax

flg = FileIO.FileExists(strFile)

Parameters

Name Required/Optional Data type Description
strFile 必須 String 存在チェックを行うファイルを指定

Return Value

Name Data type Description
flg Boolean 存在する場合:True / 存在しない場合:False

Remarks

ファイル存在チェック
指定されたファイルの存在チェックを行う

Example

  • None

See also

  • FileIO.FolderExists

まぁ、ソースとしての可読性はアレだけど Wiki になれば見栄えはばっちりなので妥協することにする。そもそも両方の見栄えというよりも

  • ソースの看板はかんたんに書けること
  • Wiki は読みやすいこと

を考えればこれでよいかと。

これを看板に書いておけば自動的にMarkDownファイルが生成されるようにする。
クラス名が「FileIO」なら「FileIO.md」がファイル名になる。

目次

目次の作成について考える。
GitHUb の Wiki に目次を生成するためには、 "_Sidebar.md" というファイルに MarkDown を書けば良い。

目次案

マニュアルのページが大きくなって、使用方法が見づらくなるので使用方法のページを先に表示する。

目次
1 使用方法
2 リファレンス
2.1 標準モジュール
2.2 インターフェイス
2.3 クラス

章番号

MarkDown で見出しを作成しても章番号が生成されないので、CSS か VBA で生成する必要がある。
GitHub の CSS って弄れるんか?よくわからん。MarkDown 生成の際に付加することにする。

生成ソース

MarkDown 生成ソースは以下の通り。VBAでVBAソースをなめるため、Excelのオプションで「VBA プロジェクト オブジェクト モデル へのアクセスを信頼する」をチェックする必要がある。
vbadev.png

あちこち見慣れないクラスが登場すると思うが、作成したライブラリの関数なので気が向いたらみてみてほしい。

Hidennotare
https://github.com/RelaxTools/Hidennotare

ドキュメント生成モジュール(Hidennotareをgitやwikiで管理するためのモジュール)
https://github.com/RelaxTools/Hidennotare/blob/master/src/Document.bas

'-----------------------------------------------------------------------------------------------------
'>### Document 標準モジュール
'>
'>**Remarks**
'>
'>- ドキュメント生成モジュール(Hidennotareをgitやwikiで管理するためのモジュール)
'>
'-----------------------------------------------------------------------------------------------------
Option Private Module
Option Explicit

'Wiki URL
Private Const TARGET_URL As String = "https://github.com/RelaxTools/Hidennotare/wiki/"
'-----------------------------------------------------------------------------------------------------
' Markdown出力
' Markdownがある行の先頭に「'>」があるものについてファイルに出力する。
'-----------------------------------------------------------------------------------------------------
Sub OutputMarkDown()

    Dim obj As Object
    Dim strFolder As String
    Dim strFile As String
    Dim SB As IStringBuilder
    Dim strBuf As String
    Dim strMark As String
    Dim i As Long
    Dim TC As IList
    Dim FP As Integer
    Dim bytBuf() As Byte
    Dim IW As IWriter

    On Error GoTo e

    '目次作成用
    Set TC = New ArrayList

    '章番号を付加するレベル
    Const Level As Long = 4

    '目次を作成するレベル
    Const ContentsLevel As Long = 3

    Dim No1() As Long
    Dim No2() As Long
    Dim No3() As Long

    ReDim No1(1 To Level)
    ReDim No2(1 To Level)
    ReDim No3(1 To Level)

    '標準モジュールのスタート
     No1(1) = 2
     No1(2) = 1
     No1(3) = 0

    'インターフェイスのスタート
     No2(1) = 2
     No2(2) = 2
     No2(3) = 0

    'クラスのスタート
     No3(1) = 2
     No3(2) = 3
     No3(3) = 0

    'Hidennotare.wiki フォルダを作成
    strFolder = ThisWorkbook.Path & ".wiki"
    FileIO.CreateFolder strFolder


    'VBComponents の取得順が アルファベット順ではないので、SortedDictionary を使用。
    Dim dic As IDictionary
    Set dic = New SortedDictionary

    For Each obj In ThisWorkbook.VBProject.VBComponents
        dic.Add obj.Name, obj
    Next

    Dim Key As Variant
    For Each Key In dic.Keys

        Set obj = dic.Item(Key)

        'モジュール名.md を作成する。
        strFile = FileIO.BuildPath(strFolder, obj.Name & ".md")

        With obj.CodeModule

            Set SB = StringBuilder.CreateObject

            For i = 1 To .CountOfLines

                '指定位置から1行取得
                strBuf = .Lines(i, 1)

                If Left$(strBuf, 2) = "'>" Then

                    '------------------------------------------
                    ' 章番号の生成
                    '------------------------------------------
                    strMark = Mid$(strBuf, 3)
                    Select Case True

                        '標準モジュール
                        Case obj.Type = 1
                           SB.Append LevelNo(strMark, No1(), Level, TC, ContentsLevel, obj.Name)

                        '1文字目が"I"、2文字目が大文字の場合、インターフェース
                        Case RegExp.Test(obj.Name, "^I[A-Z]") And obj.Name <> "ICON"
                           SB.Append LevelNo(strMark, No2(), Level, TC, ContentsLevel, obj.Name)

                        'その他クラス
                        Case Else
                           SB.Append LevelNo(strMark, No3(), Level, TC, ContentsLevel, obj.Name)

                    End Select

                End If
            Next i

            '対象があれば出力する
            If SB.Length > 0 Then

                'ファイルを空にする。
                FileIO.TruncateFile strFile

                'UTF8 & LF で保存
                With TextWriter.CreateObject(strFile, _
                                             NewLineCodeConstants.NewLineCodeNone, _
                                             EncodeConstants.EncodeUTF8, _
                                             OpenModeConstants.OpenModeOutput, _
                                             False)

                    .WriteData SB.ToString(vbLf)

                End With

            End If

            Set SB = Nothing

        End With

    Next

    'Wikiの目次作成
    If TC.Count > 0 Then

        Dim strStatic As String

        'Wikiの目次ファイル名
        strFile = FileIO.BuildPath(strFolder, "_Sidebar.md")

        '------------------------------------------
        ' 目次の静的コンテンツ部分を取得
        '------------------------------------------
        strStatic = GetStaticContents(strFile)

        'ソート
        TC.Sort New ExplorerComparer

        '目次作成
        TC.Insert 0, "#### 2 リファレンス"
        TC.Insert 1, "##### 2.1 標準モジュール"
        For i = 0 To TC.Count - 1
            If Core.StartsWith(TC.Item(i), "[2.2") Then
                TC.Insert i, "##### 2.2 インターフェイス"
                Exit For
            End If
        Next
        For i = 0 To TC.Count - 1
            If Core.StartsWith(TC.Item(i), "[2.3") Then
                TC.Insert i, "##### 2.3 クラス"
                Exit For
            End If
        Next

        '元ファイルをクリア
        FileIO.TruncateFile strFile

        '静的コンテンツと生成した目次をUTF8 & LF にて出力。
        With TextWriter.CreateObject(strFile, _
                                     NewLineCodeConstants.NewLineCodeNone, _
                                     EncodeConstants.EncodeUTF8, _
                                     OpenModeConstants.OpenModeOutput, _
                                     False)

            .WriteData strStatic
            .WriteData Join(TC.ToArray(), vbLf)

        End With
    End If

    MsgBox "生成しました!", vbInformation, "Markdown"

    Exit Sub
e:
    If Err.Number = 70 Then
        If Message.Question("他のプログラムで開いています。再試行しますか?") Then
            Message.Critical "処理を中断しました。"
        Else
            Resume
        End If
    End If
    Message.Critical "予期しないエラーが発生しました。{0},{1}", Err.Number, Err.Description
End Sub
'---------------------------------------------------
' 章番号生成
'---------------------------------------------------
Private Function LevelNo(ByVal strBuf As String, _
                         No() As Long, _
                         ByVal lngLevel As Long, _
                         TC As IList, _
                         ByVal lngContentsLevel As Long, _
                         ByVal strName As String) As String

    Dim col As Collection
    Dim SB As IStringBuilder
    Dim lngLen As Long
    Dim i As Long
    Dim strID As String

    '章番号(###~)の場合
    Set col = RegExp.Execute(strBuf, "^#+ ")
    If col.Count > 0 Then

        lngLen = Len(col(1).Value) - 1

        Dim strLeft As String
        Dim strRight As String

        strLeft = col(1).Value
        strRight = Mid$(strBuf, col(1).Length)

        '章番号生成レベル以上であれば、章番号作成
        If lngLen <= lngLevel Then

            '章番号をカウントアップ
            No(lngLen) = No(lngLen) + 1

            '現レベル以下の番号をクリア
            For i = lngLen + 1 To lngLevel
                No(i) = 0
            Next

            '章番号の生成
            Set SB = StringBuilder.CreateObject
            For i = 1 To lngLen
                SB.Append CStr(No(i))
            Next

            LevelNo = SB.ToString(".", strLeft, strRight)

            '目次作成レベル以上であれば目次作成
            If lngLen <= lngContentsLevel Then

                Dim strContent As String

                'Markdown のリンク見出しとリンク作成
                strContent = "[" & Mid$(LevelNo, InStr(LevelNo, " ") + 1) & "](" & _
                             TARGET_URL & Replace$(strName, " ", "-") & ")  "

                '目次のエリアが限られるので種類は削除
                strContent = Replace$(strContent, " クラス", "")
                strContent = Replace$(strContent, " インターフェイス", "")
                strContent = Replace$(strContent, " 標準モジュール", "")

                TC.Add strContent

            End If

        Else
            LevelNo = strBuf
        End If

        'クラス及びメソッドのアンカー作成
        Dim lngPos As Long
        lngPos = InStr(strRight, "(")

        If lngPos > 0 Then
            strID = Trim$(Mid$(strRight, 1, lngPos - 1))
        Else
            strID = Trim$(strRight)
        End If

        LevelNo = "<a name=""" & strID & """></a>" & vbLf & LevelNo

        Exit Function
    End If

    'See also のリンクを生成
    If RegExp.Test(strBuf, "^\* [A-Za-z0-9.]+") Then

        strID = Mid$(strBuf, 3)
        If UCase(strID) <> "NONE" Then
            LevelNo = "* [" & strID & "](" & TARGET_URL & Replace$(strID, ".", "#") & ")"
        Else
            LevelNo = strBuf
        End If
        Exit Function
    End If

    LevelNo = strBuf

End Function
'---------------------------------------------------
'目次から静的コンテンツ部分を抜き出す
'---------------------------------------------------
Private Function GetStaticContents(ByVal strFile As String) As String

    Dim SB As IStringBuilder
    Dim IC As ICursor

    GetStaticContents = ""

    Set SB = StringBuilder.CreateObject

    Set IC = TextReader.CreateObject(strFile, _
                                     NewLineCodeConstants.NewLineCodeLF, _
                                     EncodeConstants.EncodeUTF8)

    Do Until IC.Eof

        If Core.StartsWith(IC, "#### 2") Then
            Exit Do
        End If

        SB.Append IC

        IC.MoveNext
    Loop

    If SB.Length > 0 Then
        GetStaticContents = SB.ToString(vbLf, "", vbLf)
    End If

End Function

生成した Wiki

以下、Github Wiki に生成したマニュアルをUpしてある。
https://github.com/RelaxTools/Hidennotare/wiki

結構見やすくできたと思う。まだ間違いが沢山あると思うがとりあえず、これでメンテナンスが楽になる。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2