Scalaで「フルパスから相対パスを求める」
懲りずにやってみた。
お題はこちら。
お題:フルパスから相対パスを求める - No Programming, No Life
お題とは直接関係ないけど、例外のテストを行うための@Test(expected=classOf[HogeHoge]) をうまく略記する方法はないものでしょうか。
※仕様が追加されていたので修正しました
object RelativePath { val sepChar = '/' def getRelativePath(base: String, target: String) : String = { // 不正文字チェック def check(p: String) = { require(p != null, "Parameter must not be null") require(p.nonEmpty, "Parameter must not be empty") require(""".*[\\\?\*\:\"\|\>\<].*|.*\.$""".r.findFirstIn(p) == None, "Bad character") require(p.startsWith(sepChar.toString()), "Parameter must start with Slash") } check(base); check(target) // 末端ノードの整理 val l1 = (base.split(sepChar).filter(_.nonEmpty).toList, base.last) match { case (x, `sepChar`) => x case (x, _) => x.init } val l2 = (target.split(sepChar).filter(_.nonEmpty) ++ Array(target.last).collect{case `sepChar` => ""}).toList // 共通する上位パスを除去(分岐ノードまで降りてくる) def removeSame(l1: List[String], l2:List[String]): (List[String], List[String]) = l1 match { case hl1::tl1 if (hl1 == l2.head) => removeSame(tl1, l2.tail) case _ => (l1, l2) } removeSame(l1, l2) match { // 同レベルディレクトリ case (Nil, sl2) => sl2.mkString("./",sepChar.toString,"") // 分岐ノードまでの段数分「../」の生成 case (sl1, sl2) => (sl1.map(_ => "..") ++ sl2).mkString("",sepChar.toString,"") } } } /** 以下テスト */ class RelativePathTest { import org.junit.Assert._ import org.junit.Test import RelativePath._ @Test def testGetRelativePath = { def as(p1: String, p2: String, to: String) = assertEquals(to, getRelativePath(p1, p2)) as("/aaa/bbb/from.txt", "/aaa/bbb/to.txt", "./to.txt") as("/aaa/bbb/from.txt", "/aaa/to.txt", "../to.txt") as("/aaa/bbb/from.txt", "/aaa/bbb/ccc/to.txt", "./ccc/to.txt") as("/aaa/bbb/from.txt", "/aaa/ccc/ddd/to.txt", "../ccc/ddd/to.txt") as("/aaa/bbb/from.txt", "/ddd/ccc/to.txt", "../../ddd/ccc/to.txt") as("/aaa/bbb/", "/aaa/ddd/to", "../ddd/to") as("/aaa/bbb/", "/aaa/ccc/", "../ccc/") as("/aaa/bbb/ccc.txt", "/aaa/bbb/ccc.txt", "./ccc.txt") as("/aaa/", "/aaa/", "./") as("//aaa///bbb////from.txt", "/////aaa//////bbb///////to.txt", "./to.txt") as("//aaa///bbb///fff//from.txt", "/////aaa//////bbb///////to.txt", "../to.txt") } private def tryEx(p1: String, p2: String) : Unit= { getRelativePath(p1, p2); Unit } @Test(expected=classOf[IllegalArgumentException]) def testEmpty1 = tryEx("", "/bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testEmpty2 = tryEx("/bbb/to.txt", "") @Test(expected=classOf[IllegalArgumentException]) def testNull1 = tryEx(null, "/bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testNull2 = tryEx("/bbb/to.txt", null) @Test(expected=classOf[IllegalArgumentException]) def testStartNotSlash1 = tryEx("bbb/to.txt", "/bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testStartNotSlash2 = tryEx("/bbb/to.txt", "bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testInvChar11 = tryEx("/b\\b", "/bbb/to.txt") // 以下省略(不正文字チェックのテストを対象文字1つずつ) }