Skip to content

Commit

Permalink
Merge pull request #15 from madhead/develop
Browse files Browse the repository at this point in the history
New features!
  • Loading branch information
madhead authored Oct 24, 2017
2 parents 9216a6a + 8eb6d8a commit 207bedf
Show file tree
Hide file tree
Showing 43 changed files with 607 additions and 14 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ end_of_line = lf
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.yml]
indent_style = space
indent_size = 2

[src/test/**/*.yml]
trim_trailing_whitespace = false
5 changes: 5 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ Requires https://mermaidjs.github.io[mermaid] (version prior to `7.x`) and http:
- http://plantuml.com[`plantuml`]
No additional tools needed.

==== Custom stylesheets

Confluence allows space admins to provide custom stylesheets that override globals.
Doktor supports styling generated content by wrapping it in a `<div class="doktor">`, so you can use `.doctor` prefix in your selector to stylize content.

=== Configure Confluence servers

As you might suspect, Confluence REST API requires authentication.
Expand Down
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ val fuelVersion by project
val jsoupVersion by project
val kotlinxHtmlJvmVersion by project
val simplemagicVersion by project
val commonsCodecVersion by project
val jenkinsCredentialsPluginVersion by project
val jenkinsWorkflowStepsAPIPluginVersion by project

val testngVersion by project
val wiremockVersion by project
val mockitoVersion by project

val sezpozVersion by project

Expand All @@ -51,12 +53,14 @@ dependencies {
compile("org.jsoup:jsoup:${jsoupVersion}")
compile("org.jetbrains.kotlinx:kotlinx-html-jvm:${kotlinxHtmlJvmVersion}")
compile("com.j256.simplemagic:simplemagic:${simplemagicVersion}")
compile("commons-codec:commons-codec:${commonsCodecVersion}")

jenkinsPlugins("org.jenkins-ci.plugins:credentials:${jenkinsCredentialsPluginVersion}@jar")
jenkinsPlugins("org.jenkins-ci.plugins.workflow:workflow-step-api:${jenkinsWorkflowStepsAPIPluginVersion}@jar")

testCompile("org.testng:testng:${testngVersion}")
testCompile("com.github.tomakehurst:wiremock:${wiremockVersion}")
testCompile("org.mockito:mockito-core:${mockitoVersion}")

// SezPoz is used to process @hudson.Extension and other annotations
kapt("net.java.sezpoz:sezpoz:${sezpozVersion}")
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ fuelVersion=1.10.0
jsoupVersion=1.10.3
kotlinxHtmlJvmVersion=0.6.4
simplemagicVersion=1.12
commonsCodecVersion=1.11
jenkinsCoreVersion=2.60.1
jenkinsCredentialsPluginVersion=2.1.5
jenkinsWorkflowStepsAPIPluginVersion=2.12

testngVersion=6.11
jacksonVersion=2.9.0
wiremockVersion=2.8.0
mockitoVersion=2.11.0

sezpozVersion=1.12
22 changes: 21 additions & 1 deletion src/main/kotlin/by/dev/madhead/doktor/model/Attachment.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
package by.dev.madhead.doktor.model

import java.io.Serializable
import java.util.Arrays

data class Attachment(
val fileName: String,
val bytes: ByteArray
) : Serializable
) : Serializable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Attachment

if (fileName != other.fileName) return false
if (!Arrays.equals(bytes, other.bytes)) return false

return true
}

override fun hashCode(): Int {
var result = fileName.hashCode()

result = 31 * result + Arrays.hashCode(bytes)
return result
}
}
41 changes: 32 additions & 9 deletions src/main/kotlin/by/dev/madhead/doktor/render/DokRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import hudson.model.TaskListener
import hudson.remoting.VirtualChannel
import jenkins.SlaveToMasterFileCallable
import kotlinx.html.stream.appendHTML
import org.apache.commons.codec.digest.DigestUtils
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.parser.Parser
import java.io.File
import java.net.URI
import java.util.UUID

class DokRenderer(
val markup: Markup,
Expand All @@ -27,7 +27,10 @@ class DokRenderer(
taskListener.logger.println(Messages.doktor_render_DokRenderer_rendering(markup, file))

val content = markup.render(file)
val document = Jsoup.parse(content.content)
val document = Jsoup.parseBodyFragment(wrap(content.content))

document.outputSettings().syntax(Document.OutputSettings.Syntax.xml)

val images = processImages(document, file)

return RenderedDok(
Expand All @@ -41,7 +44,6 @@ class DokRenderer(
val result = mutableListOf<Attachment>()
val magic = ContentInfoUtil()

document.outputSettings().syntax(Document.OutputSettings.Syntax.xml)
document
.getElementsByTag("img")
.forEach {
Expand All @@ -51,8 +53,7 @@ class DokRenderer(
if (null == URI(src).host) {
val bytes = file.resolveSibling(src).readBytes()
val contentInfo = magic.findMatch(bytes)

val fileName = UUID.randomUUID().toString() + if ((null == contentInfo) || (null == contentInfo.fileExtensions)) {
val fileName = DigestUtils.md5Hex(bytes) + if ((null == contentInfo) || (null == contentInfo.fileExtensions)) {
".jpeg" // why not?
} else {
".${contentInfo.fileExtensions[0]}"
Expand All @@ -70,8 +71,15 @@ class DokRenderer(
if (!it.attr("alt").isNullOrBlank()) {
acAlt = it.attr("alt")
}
if (!it.attr("title").isNullOrBlank()) {
acAlt = it.attr("title")
// Special support for Asciidoc image titles
it.parents().find {
it.attr("class")?.contains("imageblock") ?: false
}?.let {
it.children().find {
it.attr("class")?.contains("title") ?: false
}?.let {
acTitle = it.text()
}
}
riAttachment(fileName)
}
Expand All @@ -88,8 +96,15 @@ class DokRenderer(
if (!it.attr("alt").isNullOrBlank()) {
acAlt = it.attr("alt")
}
if (!it.attr("title").isNullOrBlank()) {
acAlt = it.attr("title")
// Special support for Asciidoc image titles
it.parents().find {
it.attr("class")?.contains("imageblock") ?: false
}?.let {
it.children().find {
it.attr("class")?.contains("title") ?: false
}?.let {
acTitle = it.text()
}
}
riUrl(src)
}
Expand All @@ -102,4 +117,12 @@ class DokRenderer(

return result
}

private fun wrap(content: String): String {
return """
<div class="doktor">
${content}
</div>
"""
}
}
82 changes: 82 additions & 0 deletions src/test/kotlin/by/dev/madhead/doktor/render/DokRendererTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package by.dev.madhead.doktor.render

import by.dev.madhead.doktor.model.Markup
import by.dev.madhead.doktor.model.RenderedDok
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.github.tomakehurst.wiremock.WireMockServer
import hudson.model.TaskListener
import org.mockito.Mockito
import org.testng.Assert
import org.testng.annotations.DataProvider
import org.testng.annotations.Test
import java.io.File
import java.io.PrintStream

class DokRendererTest {
val objectMapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
lateinit private var wiremock: WireMockServer

@DataProvider(name = "markdowns")
fun markdowns(): Array<Array<*>> {
return listOf(
"local_image",
"remote_image",
"zero_image"
).map {
arrayOf(
File(this::class.java.getResource("/by/dev/madhead/doktor/render/DokRenderer/md/${it}.md").toURI()),
objectMapper.readValue<RenderedDok>(this::class.java.getResourceAsStream("/by/dev/madhead/doktor/render/DokRenderer/md/${it}.yml"))
)
}.toTypedArray()
}

@DataProvider(name = "asciidocs")
fun asciidocs(): Array<Array<*>> {
return listOf(
"local_image_alt",
"local_image_height",
"local_image_no_alt",
"local_image_title",
"local_image_title_specials",
"local_image_width",
"remote_image_alt",
"remote_image_height",
"remote_image_no_alt",
"remote_image_title",
"remote_image_title_specials",
"remote_image_width",
"zero_image"
).map {
arrayOf(
File(this::class.java.getResource("/by/dev/madhead/doktor/render/DokRenderer/adoc/${it}.adoc").toURI()),
objectMapper.readValue<RenderedDok>(this::class.java.getResourceAsStream("/by/dev/madhead/doktor/render/DokRenderer/adoc/${it}.yml"))
)
}.toTypedArray()
}

@Test(dataProvider = "markdowns")
fun markdown(input: File, expected: RenderedDok) {
test(Markup.MARKDOWN, input, expected)
}

@Test(dataProvider = "asciidocs")
fun asciidoc(input: File, expected: RenderedDok) {
test(Markup.ASCIIDOC, input, expected)
}

private fun test(markup: Markup, input: File, expected: RenderedDok) {
val taskListener = Mockito.mock(TaskListener::class.java)
val logger = Mockito.mock(PrintStream::class.java)
val renderer = DokRenderer(markup, taskListener)

Mockito.`when`(taskListener.logger).thenReturn(logger)

val renderedDok = renderer.invoke(input, null)

Assert.assertEquals(renderedDok.content, expected.content)
Assert.assertEquals(renderedDok.images, expected.images)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: local_image_alt.adoc
---

You can't see this image, but it exists:

image::../image.png[Minimal PNG]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
filePath: Not checked
content:
markup: ASCIIDOC
content: |-
<div class="doktor">
<div class="paragraph">
<p>You can’t see this image, but it exists:</p>
</div>
<div class="imageblock">
<div class="content">
<ac:image ac:alt="Minimal PNG">
<ri:attachment ri:filename="93ca32a536da1698ea979f183679af29.png"></ri:attachment>
</ac:image>
</div>
</div>
</div>
frontMatter:
title: local_image_alt.adoc
labels: []
images:
- fileName: 93ca32a536da1698ea979f183679af29.png
bytes: !!binary iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: local_image_height.adoc
---

You can't see this image, but it exists:

image::../image.png[Minimal PNG, 200, 200]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
filePath: Not checked
content:
markup: ASCIIDOC
content: |-
<div class="doktor">
<div class="paragraph">
<p>You can’t see this image, but it exists:</p>
</div>
<div class="imageblock">
<div class="content">
<ac:image ac:width="200" ac:height="200" ac:alt="Minimal PNG">
<ri:attachment ri:filename="93ca32a536da1698ea979f183679af29.png"></ri:attachment>
</ac:image>
</div>
</div>
</div>
frontMatter:
title: local_image_height.adoc
labels: []
images:
- fileName: 93ca32a536da1698ea979f183679af29.png
bytes: !!binary iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: local_image_no_alt.adoc
---

You can't see this image, but it exists and has no alt:

image::../image.png[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
filePath: Not checked
content:
markup: ASCIIDOC
content: |-
<div class="doktor">
<div class="paragraph">
<p>You can’t see this image, but it exists and has no alt:</p>
</div>
<div class="imageblock">
<div class="content">
<ac:image ac:alt="image">
<ri:attachment ri:filename="93ca32a536da1698ea979f183679af29.png"></ri:attachment>
</ac:image>
</div>
</div>
</div>
frontMatter:
title: local_image_no_alt.adoc
labels: []
images:
- fileName: 93ca32a536da1698ea979f183679af29.png
bytes: !!binary iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: local_image_title.adoc
---

You can't see this image, but it exists and has title:

.Minimal PNG
image::../image.png[Minimal PNG]
Loading

0 comments on commit 207bedf

Please sign in to comment.