2003-09-14 16:35:44 UTC
Общепризнанно, — что самодокументированная программа, это очень полезная и нужная вещь. Идея эта совсем не нова – ещё в 1975 году Ф. Брукс, в своём, Мифическом человеко-месяце, высказывал эту идею. Для классических языков программирования инструменты для составления документации по программам прямо из исходных текстов существуют уже давно, тот же Doxygen, однако для такого молодого и не очень, надо сказать, популярного языка Парсер, таких вещей пока нет.
Александр Петросян (это автор 3-го парсера) высказал эту идею уже давно, однако ни у кого руки до сих пор не доходили до создания простецкого кода, извлекающего информацию из исходных текстов и создающих простенькую документацию по коду. На самом деле и у меня бы не дошли, однако я стал запутываться в собственном коде без нормальной документации, и значит пришла пора приводить это дело в порядок.
Для тех кто не в танке – мне не досуг разбираться с Doxygen и я лучше сделаю ещё один велосипед, тем более, что для меня пока это проще.
Итак, Вашему вниманию предлагается набор методов:
- Извлекающих информацию из файлов с парсерным кодом
- Выводящих информацию (если есть) о имени класса, описание класса информацию о родительском классе.
- Выводящих информацию о методах определённых в этих файлах.
- Выводящих информацию о параметрах (если они есть), передаваемых этим методам.
- Выводящих информацию о локальных переменных методов.
- Выводящих описания методов
Код работает очень простым способом – он сканирует все каталоги, определённые в переменной $CLASS_PATH
и получает информацию обо всех .html
и .p
файлах находящихся в этих каталогах. Вывод осуществляется в 2 колонки – в левой, в зависимости от установленной куки view:
- В виде, — сначала список каталогов из
$CLASS_PATH
а затем, для выбранного каталога, список его файлов. - В виде иерархии классов.
В правой колонке выводится информация о выбранном файле с классом.
Мне этот способ (сканирование каталогов из $CLASS_PATH
) подходит – т.к. у меня весь код сосредоточен в моих классах и операторах (находящихся в каталогах определённых $CLASS_PATH
) а класс MAIN
на сайте составляют только конфигурационный auto.p
, auto.p
находящийся в корне веб-пространства и index.html
находящийся там же. В случае если это не так (код раскидан по куче html/p файлам в веб-пространстве) этот способ работать не будет, или будет, если все эти каталоги добавить в $CLASS_PATH
.
Код методов:
############################## # Вывод либо списка файлов/каталогов # либо иерархии классов # Вызывать где-нибудь внутри <body> ... </body> @content[] ^if(def $form:view){ $cookie:view[ $.value[^form:view.int(0)] $.expires(365) ] } <table class="contentCells" style="width: 100%"> <tr> <td style="width: 25%; vertical-align: top;"> ^switchView[] ^if(!^cookie:view.int(0)){ ^files[] }{ ^tree[] } </td> <td style="width: 75%; vertical-align: top;"> ^rem{/** имя файла может быть пустым поэтому может возникать ошибка чтения с диска */} ^try{ ^classInfo[$form:path/$form:fileName] }{ ^if($exception.type eq file.access || $exception.type eq file.missing){ $exception.handled(1) } } </td> </tr> </table> ############################## # Документация по классу # ВНИМАНИЕ!: В реальном коде, все переменные, используемые в методе # кроме разумеется параметра метода # сделать локальными. Я это не сделал по причине читабельности # при публикации текста в вебе @classInfo[strClass] $polyPageClass[^file::load[text;$strClass]] $polyPageClass[$polyPageClass.text] $tblPageClass[^classNameAndDesc[$polyPageClass]] # Имя класса ^if(def $tblPageClass.2){ $strPageClassName[$tblPageClass.2] }{ $strPageClassName[MAIN] } # Описание класса $strPageClassDescr[$tblPageClass.1] # Родительский класс (если есть) $strBaseClass[^baseClass[$polyPageClass]] # замена шарпов на <br/> $strPageClassDescr[^strPageClassDescr.match[#][g]{<br/>}] $strPageClassDescr[^strPageClassDescr.match[<br/>][]{}] # Вывод <h1>Класс: $strPageClassName</h1> ^if(def $strBaseClass){ $hashClasses[^classesHash[]] <h2>Родительский класс:</h2> <a href="./?path=$hashClasses.[$strBaseClass].path&fileName=$hashClasses.[$strBaseClass].file"> $strBaseClass </a> } ^if(def $strPageClassDescr){ <dl> <dt><h2>Описание:</h2></dt> <dd>$strPageClassDescr</dd> </dl> } <h2><a name="methods" id="0">Методы (коротко):</a></h2> $tblClassMethod[^methods[$polyPageClass]] # Список со ссылками на подробное описание метода <ol> $nJx(1) ^tblClassMethod.menu{ <li><a href="$request:uri#$nJx">$tblClassMethod.2</a></li> ^nJx.inc(1) } </ol> <h2>Методы (подробно):</h2> <dl> $nJx(1) ^tblClassMethod.menu{ <dt> <h3> ${nJx}. <a name="$tblClassMethod.2" id="$nJx">$tblClassMethod.2</a> [<a href="$request:uri#0">К началу</a>] </h3> </dt> ^nJx.inc(1) <dd> <dl> $strMethodDescr[$tblClassMethod.1] $strMethodDescr[^strMethodDescr.match[#][g]{<br/>}] $strMethodDescr[^strMethodDescr.match[<br/>][]{}] ^rem{/** Вывод описания метода (если есть) */} ^if(^strMethodDescr.length[] > 2){ <dt><h4>Описание:</h4></dt> <dd>$strMethodDescr</dd> } ^rem{/** Вывод списка передаваемых параметров (если есть) */} ^if(def $tblClassMethod.3){ $tblParams[^tblClassMethod.3.split[^;]] <dt><h4>Параметры:</h4></dt> <dd> $nIx(1) ^tblParams.menu{ ${nIx}. $tblParams.piece<br/> ^nIx.inc(1) } </dd> } ^rem{/** Вывод списка локальных переменных (если есть) */} ^if(def $tblClassMethod.4){ $tblLocalVars[^tblClassMethod.4.split[^;]] <dt><h4>Локальные переменные:</h4></dt> <dd> $hashLocalVars[^tblLocalVars.hash[piece]] ^tblLocalVars.menu{ - $tblLocalVars.piece<br/> } </dd> } </dl> </dd> } </dl> ############################## # Файлы и каталоги с классами @files[][tblFilesList] <h1>Каталоги</h1> <ul> <li> ^if(def $form:path){ <a href="?path=">Корень сайта</a> }{ <strong>Корень сайта</strong> } </li> ^CLASS_PATH.menu{ <li> ^if($CLASS_PATH.path ne $form:path){ <a href="?path=$CLASS_PATH.path"> $CLASS_PATH.path </a> }{ <strong>$CLASS_PATH.path</strong> } </li> } </ul> <h1>Файлы</h1> $tblFilesList[^file:list[$form:path/;\.(p|html)^$]] <ul> ^tblFilesList.menu{ <li> ^if($tblFilesList.name ne $form:fileName){ <a href="?path=$form:path&fileName=$tblFilesList.name"> $tblFilesList.name </a> }{ <strong>$tblFilesList.name</strong> } </li> } </ul> ############################## # таблица всех пользовательских классов сайта @classesTable[][tblFilesList;polyPageClass;tblClassInfo] $result[^table::create{class file path base MAIN index.html / }] ^CLASS_PATH.menu{ $tblFilesList[^file:list[$CLASS_PATH.path/;\.(p|html)^$]] ^tblFilesList.menu{ $polyPageClass[^file::load[text;$CLASS_PATH.path/$tblFilesList.name]] $polyPageClass[$polyPageClass.text] ^rem{/** Определение имени класса */} $tblClassInfo[^classNameAndDesc[$polyPageClass]] ^if(def $tblClassInfo.2){ ^result.append{$tblClassInfo.2 $tblFilesList.name $CLASS_PATH.path ^baseClass[$polyPageClass]} } } } ############################## # Хэш всех пользовательских классов сайта @classesHash[] $result[^classesTable[]] $result[^result.hash[class]] ############################## # Получает текст файла класса # Возвращает таблицу с информацией о всех методах # определённых в файле # # Раскладка по столбцам # 1 - Описание метода # 2 - Имя метода # 3 - Строка передаваемых параметров (если есть) разделённых ; # 4 - Строка локальных переменных (если есть) разделённых ; @methods[strFileText] $result[^strFileText.match[ (?: \#{30} # метка начала описания метода (30 шарпов) ([^^@]*?)? # описание метода )? (?:\n|^^) # перед @ обязательно или перевод строки или начало файла # ставить эту конструкцию именно тут - ВАЖНО! @([^^^$@^;()#]+?) # имя метода \[ (.*?) # передаваемые параметры \] (?: \[ (.*?) # локальные переменные \] )* \s ][gx]] ############################## # Определение имени и описания класса (если есть) # Получает текст файла класса # Возвращает таблицу со столбцами: # 1 - описание класса # 2 - имя класса @classNameAndDesc[strFileText] $result[^strFileText.match[ (?: \#{30} (.*?) # описание класса )? (?:\n|^^) @CLASS\n (.*?) # имя класса \n ][x]] ############################## # Определение имени родительского класса (если есть) # Получает текст файла класса # Возвращает строку с именем базового класса @baseClass[strFileText] $result[^strFileText.match[ \n @BASE \n (.+?) # имя базового класса \n ][x]] $result[$result.1] ############################## # формирование элемента дерева классов @prnTreeItem[tblItem;strChilds] $result[ <li> ^if($tblItem.file ne $form:fileName){ <a href="?path=$tblItem.path&fileName=$tblItem.file" style="font-size: 90%"> $tblItem.class </a> }{ <strong>$tblItem.class</strong> } </li> <ul>$strChilds</ul> ] ############################## # Хэш всех пользовательских классов сайта # с ключом по род. классу # Параметры: # $tblItems - таблица с элементами дерева # $strParent - столбец с идентификатором родителя @createHashTree[tblItems;strParent][tblEmpty] $tblEmpty[^table::create[$tblItems][$.limit(0)]] $result[^hash::create[]] ^tblItems.menu{ ^if(!$result.[$tblItems.[$strParent]]){ $result.[$tblItems.[$strParent]][^table::create[$tblEmpty]] } ^result.[$tblItems.[$strParent]].join[$tblItems][$.limit(1)$.offset[cur]] } ############################## # Файлы и каталоги с классами @tree[][tblClasses;hashTree] $tblClasses[^classesTable[]] ^tblClasses.sort{class} $hashTree[^createHashTree[$tblClasses;base]] $result[ <h1>Иерархия классов</h1> <ul>^prnTree[$hashTree;]</ul> ] ############################## # формирование дерева элементов # метод рекурсивно вызывает сам себя через вызов метода # prnTreeItem(формирование элемента дерева) # в $tblBrotherItems формируется список элементов одного уровня(с общим предком) @prnTree[hashTree;strBase][tblBrotherItems] ^if($hashTree.[$strBase]){ $tblBrotherItems[$hashTree.[$strBase]] ^tblBrotherItems.menu{ ^prnTreeItem[$tblBrotherItems.fields;^if($hashTree.[$tblBrotherItems.class]){^prnTree[$hashTree;$tblBrotherItems.class]}] } } ############################## # Форма переключения вида @switchView[] <h1>Показывать</h1> <form action="" method="post"> <input type="radio" name="view" value="0" checked> Список классов<br/> <input type="radio" name="view" value="1" ^if(^cookie:view.int(0)){checked}> Иерархия классов<p/> <input type="submit" value="Изменить"> </form>
Поместите этот код в понравившийся вам html файл и вызывайте метод ^content[]
в примерно таком контексте:
@main[] <html> <head> <style type="text/css"><!-- @import url(/CSS/standart.css); --></style> </head> <body> ^content[] </body> </html>
Где standart.css
стилевая таблица с требуемым оформлением используемых в документировании тегов.
Примечание: Исторически так сложилось, что у меня метка начала комментариев метода/класса — это 30 (sic!) шарпов (#) если вам это не подходит, поправьте соответствующее регулярное выражение в нужных методах (сами догадаетесь в каких).
В загружаемые исходники этого сайта включён каталог /docs/
с работающим кодом этого примера и, следовательно, исходники теперь имеют документацию.