Generate HTML source code documentation
Hi community!
Is it possible generate HTML documentation to my project ObjectScript classes (.cls)?
In Java we use Javadoc to do it. Javadoc get class comments and java metadata information and when I execute javadoc -d doc src\*, I get whole html documentation to my classes. Has IRIS something like javadoc? Is it documatic? If yes, how can I use it?
Comments
Hi @Yuri Marx ,
I don't know.
If nothing exists:
Perhaps we can write a script to call CSP.Documatic.PrintClass.cls and dump the html response into file.
ex : /csp/documatic/%25CSP.Documatic.PrintClass.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25Library.Integer
A %Net.HttpRequest is not required. I guess we can create a %request object, redirect the IO and calling OnPage method.
hi @Yuri Marx ;
In IRIS you (typically) have documatic included http://<server>:52773/csp/documatic/%25CSP.Documatic.cls
from Windows-Cube you have ClassReference .png)
in Studio you see .png)
I have no idea in VSCode.
And you get:
.png)
or in a new window:
.png)
which is straight HTML.png)
Great. But these solutions depends IRIS server online, because links are related to the documatic server. In javadoc the documentation can be read offline
Your local instance has its own local version of documatic. So, it's available offline.
It's useless on your local dev, but depending your goal : You can try to dump
zzDumpDoc(pkg, targetDir="c:\dev\testdumpdoc\")
new (pkg, targetDir)
Do:'##class(%File).DirectoryExists(targetDir) ##class(%File).CreateDirectoryChain(targetDir)
Set pkgDot = pkg _ ".", class = pkgDot, restore = 0
If $Isobject($Get(%request)) {
Set oldRequest = %request
Set oldResponse = %response
Set oldSession = %session
Set restore = 1
}
Set %request = ##class(%CSP.Request).%New()
Set %response = ##class(%CSP.Response).%New()
Set %session = ##class(%CSP.Session).%New("0123456789")
Do %session.Unlock()
Set %request.Data("PAGE",1) = "CLASS"
For {
Set class = $Order(^oddDEF(class))
Quit:$e(class,1,$l(pkgDot))'=pkgDot
Set %request.Data("LIBRARY",1) = $namespace
Set %request.Data("CLASSNAME",1) = class
Set initialIO = $IO
Set file = targetDir_class_".html"
OPEN file:("NRW"):2
USE file
Do ##class(%CSP.Documatic.PrintClass).OnPage()
USE initialIO
CLOSE file
}
If restore {
Set %request = oldRequest
Set %response = oldResponse
Set %session = oldSession
}
quit
There exits more elegant way to redirect the output (check the community).
Hi @Yuri Marx!
I did a quick and dirty try during lunch in terminal:
set %library=$namespace
set set %request=##class(%CSP.Request).%New()
set cdef=##class(%ClassDefinition).%OpenId("zrcc.EX.ISOS") ; my classname
set file="my.html" o file:("WNS"):0 write $t ; 1 if OK
use file d ##class(%CSP.Documatic).RenderClassPage(cdef,1)
close fileThe result still holds references to /csp/...images and data types
<image id=Im1 src=/csp/sys/images/ExpandedMarker.jpg height=. . .
<a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a>
that you might want to resolve as they are quite generic or you just ignore it .
My browser is very friendly :-)
And that's the result:.png)
Some polishing might be required
the generated file: ------------------------------------------------------------------------------------------ <br><p> <nobr>Klasse <b>zrcc.EX.ISOS</b> </nobr><p>Execute InterSystems ObjectScript over ODBC</p><a name="Anchor_Inventory"></a> <table class="Label" cellpadding="0" cellspacing="0" cols="1"> <tr valign="center"> <td align="left" width="100%" > <a href="#" onclick="expandIt(1); return false;" class="PackageChoice" title="Expand/Collapse Inventory" onmouseover="this.style.color = 'red';" onmouseout="this.style.color = 'black';"> <image id=Im1 src=/csp/sys/images/ExpandedMarker.jpg height=15 width=16 border=0 align=bottom> <nobr><b>Inventory</b></nobr></a> </td> </tr> </table> <br> <div id="Id1" class="DivShow" style=""> <table class="Summary" cellpadding="0" cellspacing="0" cols="1"> <tr valign="center"> <th align="center"> Parameters </th> <th align="center"> Properties </th> <th align="center"> <a href="#Anchor_Methods">Methods</a> </th> <th align="center"> Queries </th> <th align="center"> Indices </th> <th align="center"> ForeignKeys </th> <th align="center"> Triggers </th> </tr> <tr> <td align="center" class="private"> </td> <td align="center" class="private"> </td> <td align="center"> <a href="#Anchor_Methods">3</a> </td> <td align="center" class="private"> </td> <td align="center" class="private"> </td> <td align="center" class="private"> </td> <td align="center" class="private"> </td> </tr> </table> <br/> <br/></div> <a name="Anchor_Summary"></a> <table class="Label" cellpadding="0" cellspacing="0" cols="1"> <tr valign="center"> <td align="left" width="100%" > <a href="#" onclick="expandIt(2); return false;" class="PackageChoice" title="Expand/Collapse Summary" onmouseover="this.style.color = 'red';" onmouseout="this.style.color = 'black';"> <image id=Im2 src=/csp/sys/images/ExpandedMarker.jpg height=15 width=16 border=0 align=bottom> <nobr><b>Summary</b></nobr></a> </td> </tr> </table> <br> <div id="Id2" class="DivShow" style=""> <table class="Summary" border="0" cellspacing="0" cellpadding="1"> <tr><th colspan="3" title="Bold for local member; italics for inherited; shaded for private; blue for client methods; strikethrough for deprecated.">Methoden</th></tr> <tr> <td nowrap><a class="" href="#METHOD_Gset" title="zrcc.EX.ISOS">Gset</a></td> <td nowrap><a class="" href="#METHOD_Ping" title="zrcc.EX.ISOS">Ping</a></td> <td nowrap><a class="" href="#METHOD_Xcmd" title="zrcc.EX.ISOS">Xcmd</a></td> </tr> </table><br/> <br/></div> <a name="Anchor_Methods"></a> <table class="Label" cellpadding="0" cellspacing="0" cols="1"> <tr valign="center"> <td align="left" width="100%" > <a href="#" onclick="expandIt(3); return false;" class="PackageChoice" title="Expand/Collapse Methods" onmouseover="this.style.color = 'red';" onmouseout="this.style.color = 'black';"> <image id=Im3 src=/csp/sys/images/ExpandedMarker.jpg height=15 width=16 border=0 align=bottom> <nobr><b>Methods</b></nobr></a> </td> </tr> </table> <br> <div id="Id3" class="DivShow" style=""> <a name="Gset"></a> <a name="METHOD_Gset"></a> <span class="external"><nobr>• classMethode <b>Gset(glob As <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr>, subs As <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr>, val As <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr> = "", dd As <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25Integer">%Integer</a></nobr> = 1)</b> as <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr></nobr> [ SQLProc = Gset ]</span><blockquote> set / kill global over ODBC subs = subscript in ( ) dd = $data of source during global copy</blockquote> <a name="Ping"></a> <a name="METHOD_Ping"></a> <span class="external"><nobr>• classMethode <b>Ping()</b> as <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr></nobr> [ SQLProc = Ping ]</span><blockquote> check connectivity</blockquote> <a name="Xcmd"></a> <a name="METHOD_Xcmd"></a> <span class="external"><nobr>• classMethode <b>Xcmd(cmd As <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr>, ret As <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr> = "")</b> as <nobr><a href="%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=USER&CLASSNAME=%25String">%String</a></nobr></nobr> [ SQLProc = Xcmd ]</span><blockquote> eXecute Command over ODBC name of local variable for return value</blockquote> -------------------------------------------------------------------------------------------
PS: Once upon a time (when was Caché) there was a wonderful DOCBOOK database that had the methods you needed.
Here's my old (2012 sic!) article on this topic: Extending the built-in documentationRU->EN
Here you can read a little more:
Imagine you create an app to generate your project documentation using https://docusaurus.io/? Fantastic no? Better than Java!
If I try navigate the docs with iris down, Is it works?
IRIS HTML documentation is a dynamic site, depends on documatic server and source code installed into an IRIS instance. Has it static website generation, with an option to navigate without classes compiled into an IRIS instance?
Today people wants see documentation without to have install servers, inside github or local browser. Is it possible if docs is html static, but documatic is a server app.
Official online documentation for IRIS, available only online or offline as PDF only.
Class Reference Documatic, working only when the server is up and running.
No static HTML documentation at all.
I encounter this issue fairly often, but I need not a complete documentaiton but rather Interoperability production documentation.
As all the class info is also available as a %Dictionary package I just query it and generate XLSX.
Here are some queries to get started (but they usually need to be adjusted on per-project basis). Also queries should be rewritten to use SubclassOf proc instead of the current matching. Also I'm not sure why I don't pass filestream directly. That also needs to be fixed.
Queries
/// Generate docs
Class Utils.Doc
{
/// Generate Docs Tables
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"defaultSettings"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionClasses"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionSystemClasses"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionSystemClassesSettings"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionClassesSettings"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionItemsSettingsCall"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"productionItems"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"modelClasses"))
/// w $system.Status.DisplayError(##class(Utils.Doc).generate(,"modelPropeties"))
ClassMethod generate(filename As %String = {##class(%File).TempFilename("xlsx")}, query As %String, args...) As %Status
{
#dim sc As %Status = $$$OK
#dim stream As %Stream.TmpCharacter = ##class(%Stream.TmpCharacter).%New()
set sql = ..getSQL(query)
set sc = ##class(util.XLSX).generateStreamFromSQL(.stream, sql, args...)
quit:$$$ISERR(sc) sc
set file = ##class(%FileCharacterStream).%New()
set:$p(filename,".",*)'="xlsx" filename = filename _".xlsx"
set file.Filename = filename
set sc = file.CopyFrom(.stream)
quit:$$$ISERR(sc)
set sc = file.%Save()
quit sc
}
ClassMethod getSQL(name) As %String
{
#dim sc As %Status = $$$OK
set class = $case($l(name, ":"), 2:$p(name, ":"), :$classname())
set queryName = $p(name, ":", *)
if ##class(%Dictionary.QueryDefinition).IDKEYExists(class, queryName) {
set query = ##class(%Dictionary.QueryDefinition).IDKEYOpen(class, queryName,,.sc)
throw:$$$ISERR(sc) ##class(%Exception.StatusException).CreateFromStatus(sc)
set sql = query.SqlQuery
} elseif ##class(%Dictionary.XDataDefinition).IDKEYExists(class, queryName) {
#dim stream As %Stream.Object = ##class(isc.util.XmlUtils).getClassXData(class, queryName)
set sql = stream.Read($$$MaxLocalLength)
} else {
throw ##class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError, $$$FormatText("Class %1 does not have Query or XData with name %2", class, queryName)))
}
set:(removeNL = $$$YES) sql = $replace(sql, $$$NL, " ")
return sql
}
/// Generic settings for all Interoperability BH
Query defaultSettings() As %Query
{
SELECT
prop.name Setting,
TRIM('"' FROM MAX(prop.InitialExpression)) "Default",
LIST(prop.parent) Classes,
MAX(prop.Description) Descrition
FROM %Dictionary.PropertyDefinition prop
JOIN %Dictionary.CompiledParameter par ON par.parent = prop.parent AND par.Name = 'SETTINGS'
JOIN %Dictionary.CompiledClass cls ON prop.parent = cls.Name
WHERE (cls.Super LIKE '%Ens.Host%' OR cls.Name = 'Ens.Host' OR cls.Name = 'Ens.BusinessProcessBPL') AND
par."_Default" LIKE '%' || prop.Name || '%'
GROUP BY prop.Name
ORDER BY 1
}
/// Production BHs
Query productionClasses() As %Query
{
SELECT
Name Class,
Description Descrition
FROM %Dictionary.CompiledClass cls
WHERE Name LIKE 'production.%'
AND Super In ('Ens.BusinessProcessBPL', 'Ens.BusinessService', 'Ens.BusinessOperation', 'Ens.BusinessProcess')
ORDER BY 1
}
/// System BHs
Query productionSystemClasses() As %Query
{
SELECT
Name Class,
Description Descrition
FROM %Dictionary.CompiledClass cls
WHERE Name In ('util.SQLInboundAdapter', 'EnsLib.SQL.InboundAdapter', 'EnsLib.JavaGateway.Service', 'EnsLib.Workflow.Operation', 'Ens.InboundAdapter')
ORDER BY 1
}
/// System settings for BHs
Query productionSystemClassesSettings() As %Query
{
SELECT
prop.parent Class,
prop.name Setting,
TRIM('"' FROM prop.InitialExpression) "Default",
prop.Description Descrition
FROM %Dictionary.PropertyDefinition prop
JOIN %Dictionary.CompiledParameter par ON par.parent = prop.parent AND par.Name = 'SETTINGS'
JOIN %Dictionary.CompiledClass cls ON prop.parent = cls.Name
WHERE cls.Name IN ('util.SQLInboundAdapter', 'EnsLib.SQL.InboundAdapter', 'EnsLib.JavaGateway.Service', 'EnsLib.Workflow.Operation', 'Ens.InboundAdapter') AND
par."_Default" LIKE '%' || prop.Name || '%'
ORDER BY 1,2
}
/// BHs Settings
Query productionClassesSettings(production) As %Query
{
SELECT
prop.parent Class,
prop.name Setting,
TRIM('"' FROM prop.InitialExpression) "Default",
prop.Description Descrition
FROM %Dictionary.PropertyDefinition prop
JOIN %Dictionary.CompiledParameter par ON par.parent = prop.parent AND par.Name = 'SETTINGS'
JOIN %Dictionary.CompiledClass cls ON prop.parent = cls.Name
WHERE cls.Name LIKE :production || '.%' and
par."_Default" LIKE '%' || prop.Name || '%'
ORDER BY 1,2
}
/// Production BH
Query productionItems(production) As %Query
{
SELECT
Name Элемент,
ClassName Class
/*Comment Comment*/
FROM Ens_Config.Item
WHERE Production = :production
}
/// Get settings for production
Query productionItemsSettingsCall(production) As %Query
{
SELECT *
FROM util.productionItemsSettings(:production)
}
/// Get settings for production
Query productionItemsSettings(production As %String) As %Query(CONTAINID = 0, ROWSPEC = "Элемент:%String,Setting:%String,Цель:%String,Значение:%String") [ SqlName = productionItemsSettings, SqlProc ]
{
}
ClassMethod productionItemsSettingsExecute(ByRef qHandle As %Binary, production As %String) As %Status
{
set obj = ##class(Ens.Config.Production).%OpenId(production,,.sc)
quit:$$$ISERR(sc) sc
do ..clearProductionItems(.obj)
set qHandle("production") = obj
set qHandle("item") = 1
set qHandle("itemCount") = qHandle("production").Items.Count()
set qHandle("setting") = 1
set qHandle("settingCount") = qHandle("production").Items.GetAt(qHandle("item")).Settings.Count()
quit sc
}
ClassMethod productionItemsSettingsFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = productionItemsSettingsExecute ]
{
#dim sc As %Status = $$$OK
#dim item As Ens.Config.Item = qHandle("production").Items.GetAt(qHandle("item"))
#dim setting As Ens.Config.Setting = item.Settings.GetAt(qHandle("setting"))
set AtEnd = 0
set Row = $lb(item.Name, setting.Name, setting.Target, setting.Value)
if qHandle("setting")<qHandle("settingCount") {
set qHandle("setting") = qHandle("setting") + 1
} else {
if qHandle("item")<qHandle("itemCount") {
set qHandle("item") = qHandle("item") + 1
set qHandle("setting") = 1
set qHandle("settingCount") = qHandle("production").Items.GetAt(qHandle("item")).Settings.Count()
} else {
set AtEnd = 1
}
}
quit sc
}
/// Remove setting-less hosts
ClassMethod clearProductionItems(ByRef production As Ens.Config.Production)
{
#dim item As Ens.Config.Item
for i = production.Items.Count():-1:1 {
set item = production.Items.GetAt(i)
do:item.Settings.Count()=0 production.Items.RemoveAt(i)
}
}
ClassMethod productionItemsSettingsClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = productionItemsSettingsFetch ]
{
kill qHandle
quit $$$OK
}
/// Data model
Query modelClasses() As %Query
{
SELECT
Name Class,
Description Descrition
FROM %Dictionary.ClassDefinition
WHERE Name LIKE 'model.%' AND GeneratedBy IS NULL -- AND Name NOT LIKE 'model.ref.%'
}
/// Data model properties
Query modelPropeties() As %Query
{
SELECT
cls.Name Class,
prop.Name Свойство,
prop.Type Тип,
prop.Description Descrition
FROM %Dictionary.ClassDefinition cls
JOIN %Dictionary.PropertyDefinition prop ON cls.Name = prop.parent
WHERE cls.Name LIKE 'model.%' AND cls.Name NOT LIKE 'model.ref.%'
}
}