Skip to content

Commit 4c61987

Browse files
committed
OD-17115 Implementation of DSL documentation in Markdown format
1 parent 303befc commit 4c61987

26 files changed

+796
-525
lines changed

buildSrc/apidoc/src/main/groovy/au/com/ish/docs/Configuration.groovy

+13-11
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@ import org.gradle.api.file.FileCollection
1313
class Configuration {
1414

1515
public static final DSL_ANNONATION = 'ish.oncourse.API'
16+
protected static final DSL_ANNOTATION_LABEL = '@API'
1617

17-
public static final RESOUCRCES_DIRECTORY = '../buildSrc/apidoc/src/main/resources/'
18-
public static final TEMPLATE_PACKAGE = 'au.com.ish.docs.templates'
18+
public static final TEMPLATE_TYPE = 'hbs'
19+
public static final RESULT_DOC_TYPE = 'md'
20+
21+
public static final NULLABLE_ANNONATION = 'javax/annotation/Nullable'
22+
public static final NOTNULL_ANNONATION = 'javax/annotation/Nonnull'
23+
public static final CAYENNE_ENTITIES_PACKAGE = 'ish/oncourse/server/cayenne/'
1924

20-
public final String templateBaseDir
21-
public final String roomTemplate
22-
public final String chapterTemplate
25+
public static final TEMPLATES_PACKAGE = 'au.com.ish.docs.templates'
26+
public static final RESOUCRCES_DIRECTORY = '../buildSrc/apidoc/src/main/resources/'
27+
public static final CHAPTER_TEMPLATE = '/au/com/ish/docs/templates/chapter'
2328

24-
DslDocsPluginExtension dslDocsPluginExtension
29+
private DslDocsPluginExtension dslDocsPluginExtension
2530

26-
Configuration(String templateBaseDir, String roomTemplate, String chapterTemplate, DslDocsPluginExtension dslDocsPluginExtension) {
27-
this.templateBaseDir = templateBaseDir
28-
this.roomTemplate = roomTemplate
29-
this.chapterTemplate = chapterTemplate
31+
Configuration(DslDocsPluginExtension dslDocsPluginExtension) {
3032
this.dslDocsPluginExtension = dslDocsPluginExtension
3133
}
3234

@@ -39,7 +41,7 @@ class Configuration {
3941
// we also need to grab _class files in order to get the Cayenne superclasses
4042
return dslDocsPluginExtension.source
4143
.filter { it.absolutePath.endsWith(".java") || it.absolutePath.endsWith(".groovy")}
42-
.filter { it.text.contains('@API') || (it.absolutePath.contains("ish/oncourse/server/cayenne")
44+
.filter { it.text.contains(DSL_ANNOTATION_LABEL) || (it.absolutePath.contains(CAYENNE_ENTITIES_PACKAGE)
4345
&& (it.text.contains('public class') || it.text.contains('public abstract class')))
4446
}
4547
}

buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslDocsPlugin.groovy

+3-14
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ class DslDocsPlugin implements Plugin<Project> {
2424

2525
void apply(Project project) {
2626

27-
def TEMPLATE_BASEDIR = "/au/com/ish/docs/templates/"
28-
Configuration configuration = new Configuration(
29-
TEMPLATE_BASEDIR,
30-
"index.hbs",
31-
TEMPLATE_BASEDIR + "chapter",
32-
project.extensions.create(PLUGIN_NAME, DslDocsPluginExtension)
33-
)
27+
Configuration configuration = new Configuration(project.extensions.create(PLUGIN_NAME, DslDocsPluginExtension))
3428

3529
project.configurations.create(PLUGIN_NAME)
3630
.setVisible(false)
@@ -47,15 +41,10 @@ class DslDocsPlugin implements Plugin<Project> {
4741
groovyLink.setPackages("groovy.,org.codehaus.groovy.")
4842
groovyLink.setHref("http://groovy.codehaus.org/api")
4943

50-
DslGroovyDocTool docTool = new DslGroovyDocTool(
51-
configuration,
52-
[groovyLink, javaLink].toList(),
53-
project
54-
)
55-
44+
DslGroovyDocTool docTool = new DslGroovyDocTool([groovyLink, javaLink].toList(), project)
45+
docTool.cleanUpDirectory(configuration.distationDir)
5646
docTool.add(configuration.sourceFiles)
5747
docTool.renderToOutput(configuration.distationDir)
58-
5948
}
6049
}
6150
}

buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyDocTool.groovy

+67-47
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616

1717
package au.com.ish.docs
1818

19-
import au.com.ish.docs.generator.DSLGenerator
20-
import au.com.ish.docs.generator.chapter.ChapterDSLGenerator
21-
import au.com.ish.docs.generator.root.SectionDSLGenerator
19+
import au.com.ish.docs.handlebars.EmptyTemplate
20+
import au.com.ish.docs.handlebars.HandlebarsContext
21+
import au.com.ish.docs.generator.chapter.ChapterContext
22+
import au.com.ish.docs.generator.root.SectionContext
23+
import au.com.ish.docs.helpers.FileHelper
2224
import au.com.ish.docs.utils.GroovyDocUtils
25+
import com.github.jknack.handlebars.Options
2326
import groovyjarjarantlr.RecognitionException
2427
import groovyjarjarantlr.TokenStreamException
2528
import org.codehaus.groovy.groovydoc.GroovyClassDoc
@@ -30,35 +33,31 @@ import org.codehaus.groovy.tools.groovydoc.OutputTool
3033
import org.gradle.api.Project
3134
import org.gradle.api.file.FileCollection
3235

33-
import java.nio.charset.Charset
3436
/**
3537
* This is external interface to the entire tool. First we instantiate the class with the templates
3638
* and some properties. Then we add all the files we want to document (which causes them to be
3739
* immediately parsed). And then finally we generate the documentation by rendering the groovy templates.
3840
*/
3941
class DslGroovyDocTool {
4042

41-
def log
43+
def log
4244

43-
private final DslGroovyRootDocBuilder rootDocBuilder
44-
private final OutputTool output = new FileOutputTool()
45-
Project project
45+
private Project project
46+
private final DslGroovyRootDocBuilder rootDocBuilder
47+
private final OutputTool output = new FileOutputTool()
4648

47-
private Configuration configuration
48-
49-
/**
49+
/**
5050
* Let's set everything up.
5151
*
5252
* @param docTemplate Top level index template for the entire document.
5353
* @param classTemplate A template per class.
5454
* @param links Some external links to other javadocs we might want to link to
5555
*/
56-
DslGroovyDocTool(Configuration configuration, List<LinkArgument> links, Project project) {
56+
DslGroovyDocTool(List<LinkArgument> links, Project project) {
5757
this.project = project
58-
this.log = project.logger
58+
this.log = project.logger
5959
this.rootDocBuilder = new DslGroovyRootDocBuilder(this, links)
60-
this.configuration = configuration;
61-
}
60+
}
6261

6362
/**
6463
* Adding the files to the tool causes them to be parsed by the antlr based groovy or java parser
@@ -87,49 +86,70 @@ class DslGroovyDocTool {
8786
*/
8887
void renderToOutput(String destdir) throws Exception {
8988
GroovyRootDoc rootDoc = rootDocBuilder.resolve()
90-
rootDocBuilder.mergeMixins()
89+
rootDocBuilder.mergeMixins()
9190

92-
// only output classes with @API annotation
93-
def classes = rootDoc.classes().findAll { GroovyDocUtils.isVisible(it) }.toList()
94-
// rootDocBuilder.mergeTraits(classes)
91+
// only output classes with @API annotation
92+
def classes = rootDoc.classes().findAll { GroovyDocUtils.isVisible(it) }.toList()
93+
rootDocBuilder.mergeTraits(classes)
9594

96-
writeClasses(classes, destdir)
97-
writeRoot(classes, project, destdir)
95+
writeClasses(classes, destdir)
96+
writeRoot(classes, project, destdir)
9897

99-
// clean up by deleting all the empty folders
100-
def emptyDirs = []
98+
// clean up by deleting all the empty folders
99+
def emptyDirs = []
100+
project.fileTree(dir: destdir).visit { v ->
101+
File f = v.file
101102

102-
project.fileTree(dir: destdir).visit { v ->
103-
File f = v.file
103+
if (f.isDirectory() ) {
104+
def children = project.fileTree(f).filter { f.isFile() }.files
105+
if (children.size() == 0) {
106+
emptyDirs << f
107+
}
108+
}
109+
}
104110

105-
if (f.isDirectory() ) {
106-
def children = project.fileTree(f).filter { f.isFile() }.files
107-
if (children.size() == 0) {
108-
emptyDirs << f
109-
}
110-
}
111-
}
111+
// reverse so that we do the deepest folders first
112+
emptyDirs.reverseEach { it.delete() }
112113

113-
// reverse so that we do the deepest folders first
114-
emptyDirs.reverseEach { it.delete() }
114+
}
115115

116+
void cleanUpDirectory(String destDir) {
117+
File dest = new File(destDir)
118+
if (dest.exists()) {
119+
dest.deleteDir()
120+
}
116121
}
117122

118123
private void writeClasses(Collection<GroovyClassDoc> classes, String destdir) throws Exception {
119-
DSLGenerator generator = new ChapterDSLGenerator()
120-
generator.generate(classes, destdir)
124+
classes.each { classDoc ->
125+
def context = new ChapterContext.Builder()
126+
.setClassDoc(classDoc)
127+
.setClasses(classes)
128+
.setDistDir(destdir)
129+
.setProject(project)
130+
.setOutput(output)
131+
.setProject(project)
132+
.build()
133+
134+
def _context = new HandlebarsContext(["root": context])
135+
def options = new Options(null, null, null, _context, new EmptyTemplate("index.md"), null, null, null, [])
136+
FileHelper.renderChapter(classDoc, options)
137+
}
121138
}
122139

123-
private void writeRoot(Collection<GroovyClassDoc> classes, Project project, String destdir) throws Exception {
124-
//todo
125-
String path = destdir + "/index.md"
126-
output.makeOutputArea(destdir)
127-
new File(path).createNewFile()
128-
129-
SectionDSLGenerator sectionDSLGenerator = new SectionDSLGenerator(output, project, destdir)
130-
def renderedSrc = sectionDSLGenerator.generate(classes, configuration.roomTemplate)
131-
132-
output.writeToOutput(path, renderedSrc, Charset.defaultCharset().name())
133-
}
140+
private void writeRoot(Collection<GroovyClassDoc> classes, Project project, String destdir) throws Exception {
141+
String path = destdir + "/index.md"
142+
def context = new SectionContext.Builder()
143+
.setClasses(classes)
144+
.setDistDir(destdir)
145+
.setProject(project)
146+
.setOutput(output)
147+
.setProject(project)
148+
.build()
149+
150+
def _context = new HandlebarsContext(["root": context])
151+
def options = new Options(null, null, null, _context, new EmptyTemplate("index.md"), null, null, null, [])
152+
FileHelper.render(path, options)
153+
}
134154

135155
}

buildSrc/apidoc/src/main/groovy/au/com/ish/docs/DslGroovyRootDocBuilder.groovy

+30-32
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ class DslGroovyRootDocBuilder {
4646

4747
List<LinkArgument> links
4848
DslGroovyDocTool tool
49-
SimpleGroovyRootDoc rootDoc
50-
Properties properties
49+
SimpleGroovyRootDoc rootDoc
50+
Properties properties
5151

52-
def log = tool.project.logger
52+
def log = tool.project.logger
5353

5454
DslGroovyRootDocBuilder(DslGroovyDocTool tool, List<LinkArgument> links) {
5555
this.tool = tool
@@ -58,10 +58,8 @@ class DslGroovyRootDocBuilder {
5858
this.properties = new Properties()
5959
}
6060

61-
private Map<String, GroovyClassDoc> parseGroovy(String src, String packagePath, String file)
62-
throws RecognitionException, TokenStreamException {
61+
private Map<String, GroovyClassDoc> parseGroovy(String src, String packagePath, String file) throws RecognitionException, TokenStreamException {
6362
boolean isJava = file.endsWith(".java")
64-
6563
SourceBuffer sourceBuffer = new SourceBuffer()
6664
UnicodeEscapingReader unicodeReader = new UnicodeEscapingReader(new StringReader(src), sourceBuffer)
6765

@@ -103,23 +101,23 @@ class DslGroovyRootDocBuilder {
103101
if (path != null && path.length() > 0) {
104102
try {
105103
String content = ResourceGroovyMethods.getText(new File(path))
106-
rootDoc.setDescription(content)
104+
rootDoc.setDescription(content)
107105
} catch (IOException e) {
108106
log.error("Unable to load overview file", e)
109107
}
110108
}
111109
}
112110

113111
/** Extract the package name from inside the source file */
114-
private static String getPackageName(String source) {
112+
private static String getPackageName(String source) {
115113
if (StringUtils.isNotBlank(source)) {
116114
Matcher packageName = source =~ /(?m)^\s*?package\s+?([A-Za-z.0-9]+);?$/
117115
if (packageName && packageName[0] && packageName[0][1]) {
118116
return (packageName[0] as String[])[1].replaceAll(/\./, '/')
119117
}
120118
}
121119
return "DefaultPackage"
122-
}
120+
}
123121

124122
protected void processFile(File srcFile) throws IOException {
125123
def src = ResourceGroovyMethods.getText(srcFile)
@@ -129,7 +127,7 @@ class DslGroovyRootDocBuilder {
129127
SimpleGroovyPackageDoc packageDoc = (SimpleGroovyPackageDoc) rootDoc.packageNamed(packagePath)
130128

131129
try {
132-
Map<String, GroovyClassDoc> classDocs
130+
Map<String, GroovyClassDoc> classDocs
133131
classDocs = parseGroovy(src, packagePath, filename)
134132
rootDoc.putAllClasses(classDocs)
135133

@@ -149,28 +147,28 @@ class DslGroovyRootDocBuilder {
149147
return rootDoc
150148
}
151149

152-
/**
153-
* Find all the mixin classes and merge their javadoc with the class they are designed to mix to.
154-
* So ArtistMixin.class should be added to Artist.class
155-
*/
156-
void mergeMixins() {
157-
def mixins = rootDoc.classes().findAll { it.name().endsWith("Mixin") }
158-
159-
for (GroovyClassDoc mixin : mixins) {
160-
def doc = rootDoc.classes().find { it.name() == mixin.name().replace("Mixin", "") }
161-
if (doc) {
162-
mixin.methods().each { m ->
163-
log.info("Found mixin {}.{} adding to {}", mixin.name(), m.name(), doc.name())
164-
if (m.parameters() && m.parameters()[0]?.name() == "self") {
165-
// use reflection to drop the first fake 'self' param for the mixin method
166-
SimpleGroovyExecutableMemberDoc.metaClass.setAttribute(m, 'parameters', m.parameters().drop(1))
167-
((SimpleGroovyExecutableMemberDoc)m).setStatic(false)
168-
}
169-
doc.add(m)
170-
}
171-
}
172-
}
173-
}
150+
/**
151+
* Find all the mixin classes and merge their javadoc with the class they are designed to mix to.
152+
* So ArtistMixin.class should be added to Artist.class
153+
*/
154+
void mergeMixins() {
155+
def mixins = rootDoc.classes().findAll { it.name().endsWith("Mixin") }
156+
157+
for (GroovyClassDoc mixin : mixins) {
158+
def doc = rootDoc.classes().find { it.name() == mixin.name().replace("Mixin", "") }
159+
if (doc) {
160+
mixin.methods().each { m ->
161+
log.info("Found mixin {}.{} adding to {}", mixin.name(), m.name(), doc.name())
162+
if (m.parameters() && m.parameters()[0]?.name() == "self") {
163+
// use reflection to drop the first fake 'self' param for the mixin method
164+
SimpleGroovyExecutableMemberDoc.metaClass.setAttribute(m, 'parameters', m.parameters().drop(1))
165+
((SimpleGroovyExecutableMemberDoc)m).setStatic(false)
166+
}
167+
doc.add(m)
168+
}
169+
}
170+
}
171+
}
174172

175173
void mergeTraits(List<GroovyClassDoc> classes) {
176174
for (GroovyClassDoc annotatedClass : classes) {

buildSrc/apidoc/src/main/groovy/au/com/ish/docs/generator/DSLGenerator.groovy

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
package au.com.ish.docs.generator
1010

11-
import org.codehaus.groovy.groovydoc.GroovyClassDoc
11+
import au.com.ish.docs.generator.root.SectionContext
1212

13-
interface DSLGenerator {
13+
interface DSLGenerator<T extends SectionContext> {
1414

15-
String generate(Collection<GroovyClassDoc> classes, String templateName) throws Exception
15+
String generate(T context) throws Exception
1616

1717
}

0 commit comments

Comments
 (0)