/scalate-tool/src/main/scala/org/fusesource/scalate/tool/commands/ConfluenceExport.scala

http://github.com/scalate/scalate · Scala · 249 lines · 183 code · 31 blank · 35 comment · 21 complexity · 26f1700a3124ea788e43a407a8d0012d MD5 · raw file

  1. /**
  2. * Copyright (C) 2009-2011 the original author or authors.
  3. * See the notice.md file distributed with this work for additional
  4. * information regarding copyright ownership.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.fusesource.scalate.tool.commands
  19. import collection.JavaConversions
  20. import java.{util => ju, lang => jl}
  21. import java.util.zip.ZipInputStream
  22. import java.io.{FileInputStream, FileWriter, File, ByteArrayOutputStream}
  23. import java.lang.StringBuilder
  24. import org.apache.felix.gogo.commands.{Action, Option => option, Argument => argument, Command => command}
  25. import org.apache.felix.service.command.CommandSession
  26. import org.swift.common.soap.confluence.{RemotePage, RemotePageSummary, ConfluenceSoapService, ConfluenceSoapServiceServiceLocator}
  27. import collection.mutable.{HashMap, ListBuffer}
  28. import org.fusesource.scalate.util.IOUtil
  29. /**
  30. * <p>
  31. * Adding a tool that allows you to export confluence sites. Example usage:
  32. *
  33. * <code>confexport --user user --password pass https://cwiki.apache.org/confluence SM ./out</code>
  34. *
  35. * </p>
  36. *
  37. * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
  38. */
  39. @command(scope = "scalate", name = "confexport", description = "Exports a confluence space.")
  40. class ConfluenceExport extends Action {
  41. @argument(index = 0, required = true, name = "url", description = "The confluence base URL (e.g. https://cwiki.apache.org/confluence)")
  42. var url: String = "https://cwiki.apache.org/confluence"
  43. @argument(index = 1, required = true, name = "space", description = "The confluence space key")
  44. var space: String = "SM"
  45. @argument(index = 2, required = false, name = "target", description = "The target directory")
  46. var target: File = new File(".")
  47. @option(name = "--user", description = "Login user id")
  48. var user: String = _
  49. @option(name = "--password", description = "Login password")
  50. var password: String = _
  51. @option(name = "--allow-spaces", description = "(no arg) Allow spaces and other irregular chars in filenames. Use only if the O/S supports spaces in file names (e.g. Windows).")
  52. var allow_spaces: Boolean = false
  53. @option(name = "--format", description = """The format of the downloaded pages. Possible values are:
  54. page - for page files suitable for rendering in a Scalate web site. Suffix is .page
  55. conf - for a plain confluence text file without metadata. Suffix is .conf""")
  56. var format: String = "page"
  57. @option(name = "--target-db", description = "(no arg) Generate a link database for DocBook.")
  58. var target_db: Boolean = false
  59. case class Node(summary:RemotePageSummary) {
  60. val children = ListBuffer[Node]()
  61. }
  62. def execute(session: CommandSession): AnyRef = {
  63. def println(value:Any) = session.getConsole.println(value)
  64. this.execute(value => println(value) )
  65. }
  66. def execute(println: String => Unit): AnyRef = {
  67. import JavaConversions._
  68. println("downloading space index...")
  69. var confluence = serviceSetup(url + defaultConfluenceServiceExtension)
  70. if( user!=null && password!=null ) {
  71. loginToken = confluence.login(user, password);
  72. }
  73. val pageList: java.util.List[RemotePageSummary]
  74. = confluence.getPages(loginToken,space).toList
  75. var pageMap = Map( pageList.map(x=> (x.getId, Node(x))) : _ * )
  76. val rootNodes = ListBuffer[Node]()
  77. // add each node to the appropriate child collection.
  78. for( (key,node) <- pageMap ) {
  79. node.summary.getParentId match {
  80. case 0 => rootNodes += node
  81. case parentId => pageMap.get(parentId).foreach( _.children += node )
  82. }
  83. }
  84. def export(dir:File, nodes:ListBuffer[Node]):Int = {
  85. var rc = 0
  86. dir.mkdirs
  87. nodes.foreach { node=>
  88. val sanitized_title = sanitize(node.summary.getTitle);
  89. val page = confluence.getPage(loginToken, node.summary.getId);
  90. var content:String = "";
  91. var file_suffix = ".page";
  92. if (format.equalsIgnoreCase("page")) {
  93. file_suffix = ".page"
  94. content = """---
  95. title: """+page.getTitle+"""
  96. page_version: """+page.getVersion+"""
  97. page_creator: """+page.getCreator+"""
  98. page_modifier: """+page.getModifier+"""
  99. --- pipeline:conf
  100. """
  101. }
  102. else if (format.equalsIgnoreCase("conf")) {
  103. file_suffix = ".conf"
  104. }
  105. content += page.getContent
  106. val file = new File(dir, sanitized_title + file_suffix)
  107. println("downloading: \u001B[1;32m"+file+"\u001B[0m")
  108. IOUtil.writeText(file, content)
  109. rc += 1
  110. if (target_db) {
  111. TargetDB.startDiv(
  112. sanitized_title, // targetptr (used in DocBook olinks)
  113. page.getTitle // Page title
  114. )
  115. }
  116. if( !node.children.isEmpty ) {
  117. rc += export(new File(dir, sanitized_title), node.children)
  118. }
  119. if (target_db) {
  120. TargetDB.endDiv()
  121. }
  122. }
  123. rc
  124. }
  125. def sanitize(title:String): String = {
  126. if (allow_spaces) {
  127. title.replaceAll("\\\"", "")
  128. }
  129. else {
  130. title.toLowerCase.replaceAll(" ","-").replaceAll("[^a-zA-Z_0-9\\-\\.]", "")
  131. }
  132. }
  133. if (target_db) {
  134. TargetDB.rootDir = target
  135. TargetDB.init(space, space)
  136. }
  137. val total = export(target, rootNodes);
  138. if (target_db) {
  139. TargetDB.close()
  140. }
  141. println("Exported \u001B[1;32m%d\u001B[0m page(s)".format(total));
  142. confluence.logout(loginToken)
  143. null
  144. }
  145. //-----
  146. // Definitions to set up the Confluence SOAP-RPC service
  147. //
  148. var confluence: ConfluenceSoapService = null
  149. var loginToken: String = null
  150. val defaultConfluenceServiceExtension = "/rpc/soap-axis/confluenceservice-v1" // Confluence soap service
  151. def serviceSetup(address: String): ConfluenceSoapService = {
  152. var serviceLocator = new ConfluenceSoapServiceServiceLocator
  153. serviceLocator.setConfluenceserviceV2EndpointAddress(address);
  154. serviceLocator.getConfluenceserviceV2();
  155. }
  156. //-----
  157. // The TargetDB object enables the generation of a DocBook
  158. // link database, target.db. Using the link database, a DocBook
  159. // document can then easily cross-reference a confluence page.
  160. object TargetDB {
  161. var rootDir = new File(".")
  162. var rootFileName = "index.html"
  163. var targetDBFileName = "target.db"
  164. var level: Int = 0
  165. var targetContent: StringBuffer = new StringBuffer
  166. var hrefStack = new java.util.Stack[String]
  167. def init(targetptr: String, title: String) {
  168. targetContent.append(
  169. "<div element=\"book\" href=\"" + rootFileName
  170. + "\" number=\"\" targetptr=\""
  171. + targetptr + "\">\n"
  172. + " <ttl>" + title + "</ttl>\n"
  173. + " <xreftext>" + title + "</xreftext>\n"
  174. )
  175. level = 1
  176. }
  177. def close() {
  178. while (level > 0) { endDiv() }
  179. val file= new File(rootDir, targetDBFileName)
  180. IOUtil.writeText(file, targetContent.toString())
  181. }
  182. def startDiv(targetptr: String, title: String) {
  183. val indent = " " * level
  184. var escTitle = escapeXml(title)
  185. appendAndPush(hrefStack, targetptr)
  186. var href = hrefStack.peek + ".html"
  187. targetContent.append(
  188. indent + "<div element=\"" + getDivElementName() + "\" href=\""
  189. + href + "\" number=\"\" targetptr=\""
  190. + targetptr + "\">\n"
  191. + indent + " <ttl>" + escTitle + "</ttl>\n"
  192. + indent + " <xreftext>" + escTitle + "</xreftext>\n"
  193. )
  194. level += 1
  195. }
  196. def endDiv() {
  197. level -= 1
  198. if ( ! hrefStack.empty) hrefStack.pop
  199. targetContent.append(" " * level + "</div>\n")
  200. }
  201. protected def getDivElementName(): String = {
  202. if (level==0) { "book" }
  203. else if (level==1) { "chapter" }
  204. else { "section" }
  205. }
  206. protected def escapeXml(text: String): String = {
  207. text.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
  208. }
  209. protected def appendAndPush(stack: java.util.Stack[String], segment: String) {
  210. if (stack.empty) {
  211. stack.push(segment)
  212. }
  213. else {
  214. stack.push(stack.peek() + "/" + segment)
  215. }
  216. }
  217. }
  218. }