経緯
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 プロジェクト オブジェクト モデル へのアクセスを信頼する」をチェックする必要がある。
あちこち見慣れないクラスが登場すると思うが、作成したライブラリの関数なので気が向いたらみてみてほしい。
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
結構見やすくできたと思う。まだ間違いが沢山あると思うがとりあえず、これでメンテナンスが楽になる。