Written by

Software Architect at Visum
Question Yuri Marx · Nov 24, 2020

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

Lorenzo Scalese · Nov 24, 2020

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.

0
Yuri Marx  Nov 24, 2020 to Robert Cemper

Great. But these solutions depends IRIS server online, because links are related to the documatic server. In javadoc the documentation can be read offline

0
Dmitry Maslennikov  Nov 24, 2020 to Yuri Marx

Your local instance has its own local version of documatic. So, it's available offline.

0
Lorenzo Scalese  Nov 24, 2020 to Yuri Marx

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).

0
Robert Cemper  Nov 24, 2020 to Yuri Marx

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 file

The 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:

Some polishing might be required
 

0
Robert Cemper  Nov 24, 2020 to Robert Cemper
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>&#149 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>&#149 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>&#149 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>
-------------------------------------------------------------------------------------------
​​​
0
Yuri Marx  Nov 24, 2020 to Vitaliy Serdtsev

Imagine you create an app to generate your project documentation using https://docusaurus.io/? Fantastic no? Better than Java!

0
Yuri Marx · Nov 24, 2020

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.

0
Dmitry Maslennikov  Nov 24, 2020 to Yuri Marx

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.

0
Eduard Lebedyuk · Nov 24, 2020

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.%'
}

}
0