import Scalaz._
object TSV {
- type Header = Seq[Symbol]
- type Field = String
- type Record = Map[Symbol, Field]
-
- // ---
- type Failure[ F[_], R] = String => F[R]
- type Success[A, F[_], R] = A => F[R]
-
- trait Parser[A] {
- def runParser[F[_], R](kf: Failure[F, R], ks: Success[A, F, R]): F[R]
- }
- implicit def ParserFunctor = new Functor[Parser] {
+ implicit val ParserFunctor = new Functor[Parser] {
def map[A, B](m: Parser[A])(f: A => B) = new Parser[B] {
def runParser[F[_], R](kf: Failure[F, R], ks: Success[B, F, R]): F[R] = {
def ks_(a: A) = ks(f(a))
}
}
- implicit def ParserApplicative = new Applicative[Parser] {
+ implicit val ParserApplicative = new Applicative[Parser] {
def point[A](a: => A) = new Parser[A] {
def runParser[F[_], R](kf: Failure[F, R], ks: Success[A, F, R]): F[R]
= ks(a)
}
}
- implicit def ParserMonad = new Monad[Parser] {
+ implicit val ParserMonad = new Monad[Parser] {
def point[A](a: => A) = a.point[Parser]
def bind[A, B](m: Parser[A])(f: A => Parser[B]) = new Parser[B] {
}
}
- implicit def ParserMonadError = new MonadError[({type λ[String, α] = Parser[α]})#λ, String] {
+ implicit val ParserMonadError = new MonadError[({type λ[String, α] = Parser[α]})#λ, String] {
def point[A](a: => A) = a.point[Parser]
def bind[A, B](m: Parser[A])(f: A => Parser[B]) = m.flatMap(f)
= kf(e)
}
}
+
+ implicit def ToMonadErrorOps[A](m: Parser[A]) = new {
+ def handleError(f: String => Parser[A]): Parser[A]
+ = implicitly[MonadError[({type λ[String, α] = Parser[α]})#λ, String]].handleError(m)(f)
+ }
+
implicit def ToMonadErrorIdOps(e: String) = new {
def raiseError[A]: Parser[A]
= implicitly[MonadError[({type λ[String, α] = Parser[α]})#λ, String]].raiseError(e)
}
+ type Header = Seq[Symbol]
+
+ case class Field(value: String) {
+ def parseField[A: FromField]
+ = implicitly[FromField[A]].parseField(Field(value))
+ }
+
+ case class Record(value: Map[Symbol, Field]) {
+ def parseRecord[A: FromRecord]
+ = implicitly[FromRecord[A]].parseRecord(Record(value))
+
+ def lookup[A: FromField](name: Symbol): Parser[A]
+ = value.get(name).fold(("no field named " + name.name).raiseError[A])(_.parseField[A])
+ }
+
+ type Failure[ F[_], R] = String => F[R]
+ type Success[A, F[_], R] = A => F[R]
+
+ trait Parser[+A] {
+ def runParser[F[_], R](kf: Failure[F, R], ks: Success[A, F, R]): F[R]
+ }
+
def runParser[A](p: Parser[A]): Either[String, A] = {
type F[R] = Either[String, R]
def left (e: String): F[A] = Left(e)
def right(x: A ): F[A] = Right(x)
return p.runParser(left, right)
}
- // ---
-
- def decode[A: FromRecord](tsv: StringLike[_]): Seq[A] = {
- val lines = tsv.split('\n').filter(!_.isEmpty)
- val header = lines.head.split('\t').map(Symbol(_))
- val body = lines.tail.map(line => (header zip line.split('\t')).toMap)
- return body.map(implicitly[FromRecord[A]].parseRecord)
- }
trait FromRecord[A] {
- def parseRecord(r: Record): A
+ def parseRecord(r: Record): Parser[A]
}
trait FromField[A] {
def parseField(f: Field): Parser[A]
}
- implicit def toFromFieldOps(f: Field) = new {
- def parseField[A: FromField]
- = implicitly[FromField[A]].parseField(f)
- }
trait ToRecord[A] {
def toRecord(a: A): Record
def toField(a: A): Field
}
+ class DecodeFailedException private(e: RuntimeException) extends RuntimeException(e) {
+ def this(msg: String) = this(new RuntimeException(msg))
+ def this(msg: String, cause: Throwable) = this(new RuntimeException(msg, cause))
+ }
+
+ def decode[A: FromRecord](tsv: StringLike[_]): Seq[A] = {
+ val lines = tsv.split('\n').filter(!_.isEmpty)
+ val header = lines.head.split('\t').map(Symbol(_))
+ val body = lines.tail.map(line => Record((header zip line.split('\t').map { Field(_) }).toMap))
+ return body.map { r =>
+ runParser(r.parseRecord[A]) match {
+ case Right(a) => a
+ case Left(e) => throw new DecodeFailedException(e)
+ }
+ }
+ }
+
// Option[A]
implicit def OptionFromField[A: FromField] = new FromField[Option[A]] {
def parseField(f: Field): Parser[Option[A]] = {
- if (f.isEmpty) {
- return (None : Option[A]).point[Parser]
+ if (f.value.isEmpty) {
+ return None.point[Parser]
}
else {
f.parseField[A].map { Some(_) }
}
// String
- implicit def StringFromField = new FromField[String] {
- def parseField(f: Field): Parser[String] = f.point[Parser]
+ implicit val StringFromField = new FromField[String] {
+ def parseField(f: Field): Parser[String] = f.value.point[Parser]
}
// Int
- implicit def IntFromField = new FromField[Int] {
+ implicit val IntFromField = new FromField[Int] {
def parseField(f: Field): Parser[Int] = {
try {
- f.toInt.point[Parser]
+ f.value.toInt.point[Parser]
}
catch {
case e: NumberFormatException =>
package jp.ymir.taskReporter.core
import java.util.Calendar
import java.util.GregorianCalendar
+import scala.language.reflectiveCalls
import scala.util.matching.Regex
+import scalaz._
+import Scalaz._
case class Task(
date: Calendar,
sealed abstract class Status
object Status {
- case object DoingFine extends Status
- case object Lagging extends Status
- case object DeadlineChanged extends Status
- case object Completed extends Status
+ case object DoingFine extends Status { override def toString() = "順調" }
+ case object Lagging extends Status { override def toString() = "遅延" }
+ case object DeadlineChanged extends Status { override def toString() = "期限変更" }
+ case object Completed extends Status { override def toString() = "完了" }
}
- implicit def CalendarFromField = new FromField[Calendar] {
+ implicit val CalendarFromField = new FromField[Calendar] {
val pattern = """^(\d{4})/(\d{2})/(\d{2})$""".r
- def parseField(f: Field): Calendar = {
- f match {
- case pattern(year, month, day) =>
- new GregorianCalendar(year.toInt, month.toInt, day.toInt)
- }
- }
- }
-
- implicit def StatusFromField = new FromField[Status] {
- def parseField(f: Field): Status = f match {
- case "順調" => Status.DoingFine
- case "遅延" => Status.Lagging
- case "期限変更" => Status.DeadlineChanged
- case "完了" => Status.Completed
- }
- }
-
- implicit def TaskFromRecord = new FromRecord[Task] {
- def parseRecord(r: Record): Task = {
- return Task(
- date = parseField[Calendar](r('報告日)),
- ticketID = parseField[Option[Int]](r('チケットID)),
- title = parseField[String](r('作業名)),
- expectedCompletionDate = parseField[Calendar](r('作業完了予定年月日)),
- deadline = parseField[Option[Calendar]](r('タスク期限)),
- status = parseField[Status](r('状態)),
- description = parseField[Option[String]](r('説明))
- )
- }
- }
-}
-
-/*
-class Task(tsvLine: String) {
- sealed abstract class Status
- object Status {
- case object NoProgress extends Status
- case object DoingFine extends Status
- case object Lagging extends Status
- case object WillDelay extends Status
- case object DeadlineChanged extends Status
- case object Completed extends Status
- }
-
- class InvalidNumberOfColumnsException private(e: RuntimeException) extends RuntimeException(e) {
- def this(msg: String) = this(new RuntimeException(msg))
- def this(msg: String, cause: Throwable) = this(new RuntimeException(msg, cause))
- }
-
- private val cols = tsvLine.split("\\t")
- if (cols.length != 7) {
- throw new InvalidNumberOfColumnsException(tsvLine)
- }
-
- var date : Calendar = {
- val pattern = """^(?:報告日:)?(\d{4})/(\d{2})/(\d{2})$""".r
- cols(0) match {
+ def parseField(f: Field): Parser[Calendar] = f.value match {
case pattern(year, month, day) =>
- new GregorianCalendar(year.toInt, month.toInt, day.toInt)
- }
- }
-
- var ticketID : Int = {
- val pattern = """^(?:チケットID:)?(\d+)$""".r
- cols(1) match {
- case pattern(id) => id.toInt
- }
- }
-
- var title : String = {
- val pattern = """^(?:作業名:)?(.*)$""".r
- cols(2) match {
- case pattern(title) => title
- }
- }
-
- var tentativeDeadline : Option[Calendar] = {
- val pattern = """^(?:仮期限:)?(\d{4})/(\d{2})/(\d{2})$""".r
- cols(3) match {
- case pattern(year, month, day) =>
- Some(new GregorianCalendar(year.toInt, month.toInt, day.toInt))
+ new GregorianCalendar(year.toInt, month.toInt - 1, day.toInt).point[Parser]
case _ =>
- None
+ ("date not in format yyyy/MM/dd: " + f).raiseError[Calendar]
}
}
- var deadline : Option[Calendar] = {
- val pattern = """^(?:期限:)?(\d{4})/(\d{2})/(\d{2})$""".r
- cols(4) match {
- case pattern(year, month, day) =>
- Some(new GregorianCalendar(year.toInt, month.toInt, day.toInt))
+ implicit val StatusFromField = new FromField[Status] {
+ def parseField(f: Field): Parser[Status] = f.value match {
+ case "順調" => (Status.DoingFine: Status).point[Parser]
+ case "遅延" => (Status.Lagging: Status).point[Parser]
+ case "期限変更" => (Status.DeadlineChanged: Status).point[Parser]
+ case "完了" => (Status.Completed: Status).point[Parser]
case _ =>
- None
- }
- }
-
- var status : Status = {
- val pattern = """^(?:状態:)?(.+)$""".r
- cols(5) match {
- case pattern(s) =>
- s match {
- case "未作業" => Status.NoProgress
- case "順調" => Status.DoingFine
- case "悪化" => Status.Lagging
- case "遅延" => Status.WillDelay
- case "期限変更" => Status.DeadlineChanged
- case "完了" => Status.Completed
- }
+ ("invalid status: " + f).raiseError[Status]
}
}
- var supplement : String = {
- val pattern = """^(?:補足:)?(.*)$""".r
- cols(6) match {
- case pattern(title) => title
- }
+ implicit val TaskFromRecord = new FromRecord[Task] {
+ def parseRecord(r: Record): Parser[Task] =
+ for {
+ date <- r.lookup[Calendar ]('報告日)
+ ticketID <- r.lookup[Option[Int] ]('チケットID)
+ title <- r.lookup[String ]('作業名)
+ expectedCompletionDate <- r.lookup[Calendar ]('作業完了予定年月日)
+ deadline <- r.lookup[Option[Calendar]]('タスク期限)
+ status <- r.lookup[Status ]('状態)
+ description <- r.lookup[Option[String] ]('説明).handleError(_ => None.point[Parser])
+ }
+ yield Task(date, ticketID, title, expectedCompletionDate,
+ deadline, status, description)
}
}
- */