/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx import scala.annotation.implicitNotFound trait FileKind{ def exts:List[String] //the precedence has meaning as the most common type and preferred conversion format def defaultExt:String = exts.headOption.getOrElse("undefined") override def toString = defaultExt private lazy val extsNormalized = exts.map{_.toLowerCase}.toSet final def is(f:File):Boolean = unapply(f) final def unapply(f:File):Boolean = extsNormalized contains f.ext.toLowerCase } object FileKind{ object Markdown extends FileKind {def exts = List("markdown","md","kson")} object Word extends FileKind {def exts = List("docx","doc")} //these do not need to be case objects since unapply is defiend object Img extends FileKind {def exts = List("emf","png")} //TODO add more and add the imagaicmagic converter def convert[A <: FileKind, B <: FileKind](src:File, tgt:File) (implicit fc:FileConverter[A,B], ec:EC):Future[File] = fc.convert(src,tgt)(ec) } @implicitNotFound("No FileConverter found for FileKind ${A} to ${B}") //string interpolation for these guys can not be prepended with s for StringContext trait FileConverter[A <: FileKind,B <: FileKind]{ //<: is the subtype type parameter bounds def convert(src:File, tgt:File)(implicit ec:EC):Future[File] def defaultTargetExt:String def convert(src:File)(implicit ec:EC):Future[File] = convert(src, src.companion(defaultTargetExt)) } /* Collection of default FileConverter type classes Wow so much common boiler plate is required for the powerful use of type classes ... the concept of this file and all the types and plurality of types is just to remove relying on runtime pattern matching of the FileKind and let external users provide FileKinds and FileConverters (src,tgt) match { case (Word(), Markdown()) => run(Shell("pandoc -f docx -t markdown -o", tgt.path, src.path)) case (Markdown(), Word()) => run(Shell("pandoc -f markdown -t docx -o", tgt.path, src.path)) //TODO add toc elements case _ => Future.failed(new java.lang.Throwable(s"no converter for file type ${src.ext} to ${tgt.ext}")) } */ object FileConverter{ import FileKind._ private def run(shell:Shell,tgt:File)(implicit ec:EC):Future[File] = { // println(s"shell:$shell, tgt:${tgt.canon}) shell.exitCode.flatMap{ case 0 => Future(tgt) case x => Future.failed(new java.lang.Throwable(s"failed to convert with ${shell} $x")) } } //TODO add css file copied over from the resource directory private val pandocMarkdown = "markdown+simple_tables+yaml_metadata_block+raw_html" private def pandoc(from:String, to:String, src:File, tgt:File)(implicit ec:EC):Future[File] = { val cssFile = src.companion("css") val cssFlags = if(cssFile.isFile) s" --css=${cssFile.name}" else "" val shell = Shell(s"pandoc $cssFlags --from $from --to $to --standalone --atx-headers --section-divs --output") println(shell) //TODO run(shell.apply(tgt.path, src.path), tgt) } //FIXME focuse on using this single method for the converters rather than the typeclasss since file ext can not be known ahead of time anyway def convert(src:File, tgt:File)(implicit ec:EC):Future[File] = { (src.ext, tgt.ext) match { case ("md", fmt) => pandoc(pandocMarkdown, fmt, src, tgt) case ("markdown", fmt) => pandoc(pandocMarkdown, fmt, src, tgt) case ("docx", "markdown") => pandoc(pandocMarkdown, "markdown", src, tgt) case _ => Future.failed(new java.lang.IllegalArgumentException(s"no converter for ${src.ext} to ${tgt.ext}")) } } implicit object MarkdownToWord extends FileConverter[Markdown.type,Word.type]{ def convert(src:File, tgt:File)(implicit ec:EC):Future[File] = pandoc(pandocMarkdown, "docx", src, tgt) def defaultTargetExt = Word.defaultExt } implicit object WordToMarkdown extends FileConverter[Word.type, Markdown.type]{ def convert(src:File, tgt:File)(implicit ec:EC):Future[File] = pandoc("docx", pandocMarkdown, src, tgt) def defaultTargetExt = Markdown.defaultExt } } trait FileConverters{ def convert(src:File,tgt:File)(implicit ec:EC):Future[File] def kinds:Set[FileKind] } object FileConverters{ //auto detect kinds at runtime implicit object DefaultFileConverters extends FileConverters{ import FileKind._ import FileConverter._ val kinds = Set(Markdown,Word) def convert(src:File,tgt:File)(implicit ec:EC):Future[File] = (src, tgt) match { case (Markdown(), Word()) => MarkdownToWord.convert(src,tgt) case (Word(), Markdown()) => WordToMarkdown.convert(src,tgt) case _ => Future.failed(new java.lang.Throwable(s"no converter for file type ${src.ext} to ${tgt.ext}")) } } }