2003-09-14 16:35:44 UTC

Общепризнанно, — что самодокументированная программа, это очень полезная и нужная вещь. Идея эта совсем не нова – ещё в 1975 году Ф. Брукс, в своём, Мифическом человеко-месяце, высказывал эту идею. Для классических языков программирования инструменты для составления документации по программам прямо из исходных текстов существуют уже давно, тот же Doxygen, однако для такого молодого и не очень, надо сказать, популярного языка Парсер, таких вещей пока нет.

Александр Петросян (это автор 3-го парсера) высказал эту идею уже давно, однако ни у кого руки до сих пор не доходили до создания простецкого кода, извлекающего информацию из исходных текстов и создающих простенькую документацию по коду. На самом деле и у меня бы не дошли, однако я стал запутываться в собственном коде без нормальной документации, и значит пришла пора приводить это дело в порядок.

Для тех кто не в танке – мне не досуг разбираться с Doxygen и я лучше сделаю ещё один велосипед, тем более, что для меня пока это проще.

Итак, Вашему вниманию предлагается набор методов:

  1. Извлекающих информацию из файлов с парсерным кодом
  2. Выводящих информацию (если есть) о имени класса, описание класса информацию о родительском классе.
  3. Выводящих информацию о методах определённых в этих файлах.
  4. Выводящих информацию о параметрах (если они есть), передаваемых этим методам.
  5. Выводящих информацию о локальных переменных методов.
  6. Выводящих описания методов

Код работает очень простым способом – он сканирует все каталоги, определённые в переменной $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/ с работающим кодом этого примера и, следовательно, исходники теперь имеют документацию.

2003-09-14 16:35:44 UTC parser snippet web