Skip to content

Commit

Permalink
Merge pull request #8 from rchillyard/V1_0_5
Browse files Browse the repository at this point in the history
V1 0 5
  • Loading branch information
rchillyard authored Apr 15, 2019
2 parents c70bb36 + 217994c commit 96369fc
Show file tree
Hide file tree
Showing 21 changed files with 660 additions and 223 deletions.
43 changes: 39 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ together with something like, for instance, a Json writer.

# User Guide

Current version: 1.0.4.
Current version: 1.0.5.

See release notes below for history.

Expand Down Expand Up @@ -120,6 +120,7 @@ The type _Row_ defines the specific row type (for example, _Movie_, in the examp
_hasHeader_ is used to define if there is a header row in the first line of the file (or sequence of strings) to be parsed.
_forgiving_, which defaults to _false_, can be set to _true_ if you expect that some rows will not parse, but where this
will not invalidate your dataset as a whole.

In forgiving mode, any exceptions thrown in the parsing of a row are collected and then printed to _System.err_ at the conclusion of the parsing of the table.
_rowParser_ is the specific parser for the _Row_ type (see below).
_builder_ is used by the _parse_ method.
Expand Down Expand Up @@ -321,7 +322,38 @@ Also, note that the instance of _ColumnHelper_ defined here has the formatter de
Rendering
=========

TableParser provides a mechanism for rendering a table to a hierarchical (i.e. tree-structured) output.
_TableParser_ provides two mechanisms for rendering a table:
* one to a straight serialized output, for example, when rendering a table as a CSV file.
* the other to a hierarchical (i.e. tree-structured) output, such as an HTML file.

## Non-hierarchical output

For this type of output, the application programmer must provide an instance of _Writer[O]_ where is, for example a _StringBuilder_,
_BufferedOutput_, or perhaps an I/O Monad.

The non-hierarchical output does not support the same customization of renderings as does the hierarchical output.
It's intended more as a straight, quick-and-dirty output mechanism to a CSV file.

Here, for example, is an appropriate definition.

implicit object StringBuilderWriteable extends Writable[StringBuilder] {
override def writeRaw(o: StringBuilder)(x: CharSequence): StringBuilder = o.append(x.toString)
override def unit: StringBuilder = new StringBuilder
override def delimiter: CharSequence = "|"
}

The default _delimiter_ is ", ".
You can override the _newline_ and _quote_ methods too if you don't want the defaults.

And then, folllowing this, you will write something like the following code:

print(table.render.toString)

The _Writable_ object will take care of inserting the delimiter and quotes as appropriate.
Columns will appear in the same order as the parameters of _Row_ type (which must be either a _Product_, such as a case class, or an _Array_ or a _Seq_).
If you need to change the order of the rows, you will need to override the _writeRow_ method of _Writable_.

## Hierarchical output

A type class called _TreeWriter_ is the main type for rendering.
One of the instance methods of _Table[Row]_ is a method as follows:
Expand Down Expand Up @@ -378,6 +410,9 @@ If you need to set HTML attributes for a specific type, for example a row in the
Release Notes
=============

V1.0.4 -> V1.0.5
* Added a convenient way of rendering a table as a non-hierarchical structure. In other words, serialization to a CSV file.

V1.0.3 -> V1.0.4
* Added the ability to add header row and header column for tables (NOTE: not finalized yet, but functional).

Expand All @@ -396,7 +431,7 @@ V1.0.0 -> V.1.0.1
* Fixed Issue #1;
* Added parsing of Seq[Seq[String]];
* Added cellParserRepetition;
* Implemented closing of Source in Table.parse methods;
* Added encoding parameters to Table.parse methods.
* Implemented closing of Source in _Table.parse_ methods;
* Added encoding parameters to _Table.parse_ methods.


2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ organization := "com.phasmidsoftware"

name := "TableParser"

version := "1.0.4"
version := "1.0.5"

scalaVersion := "2.12.6"

Expand Down
3 changes: 0 additions & 3 deletions src/main/scala/com/phasmidsoftware/parse/CellParsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,7 @@ trait CellParsers {
construct(p1V)
}

// TODO We need to allow for a single-parameter conversion of String => T via P1
// This fixes issue #1
override def convertString(w: String): T = construct(implicitly[CellParser[P1]].convertString(w))

}
}

Expand Down
4 changes: 0 additions & 4 deletions src/main/scala/com/phasmidsoftware/parse/RowParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ case class StandardRowParser[Row: CellParser](parser: LineParser) extends String

object StandardRowParser {
def apply[Row: CellParser](implicit rowConfig: RowConfig): StandardRowParser[Row] = StandardRowParser(LineParser.apply)

// CONSIDER eliminating as this is never used.
def apply[Row: CellParser](delimiter: Regex, string: Regex, enclosures: String, listSeparator: Char, quote: Char): StandardRowParser[Row] =
StandardRowParser(new LineParser(delimiter, string, enclosures, listSeparator, quote))
}

/**
Expand Down
42 changes: 38 additions & 4 deletions src/main/scala/com/phasmidsoftware/parse/TableParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,53 @@ import com.phasmidsoftware.util.FP
import scala.annotation.implicitNotFound
import scala.util.{Failure, Try}

/**
* Type class to parse a set of rows as a Table.
*
* @tparam Table the Table type.
*/
@implicitNotFound(msg = "Cannot find an implicit instance of TableParser[${Table}]. Typically, you should define an instance of StringTableParser or StringsTableParser.")
trait TableParser[Table] {

/**
* The row type.
*/
type Row

/**
* The input type.
*/
type Input

/**
* NOTE: this method must be consistent with the builder method.
*
* @return true if this table parser should provide a header.
*/
def hasHeader: Boolean

/**
* NOTE: this method must be consistent with the hasHeader method.
*
* @param rows the rows which will make up the table.
* @param maybeHeader an optional Header.
* @return an instance of Table.
*/
def builder(rows: Seq[Row], maybeHeader: Option[Header]): Table

/**
* Method to determine how errors are handled.
*
* @return true if individual errors are logged but do not cause parsing to fail.
*/
def forgiving: Boolean = false

/**
* Method to define a row parser.
*
* @return a RowParser[Row, Input].
*/
def rowParser: RowParser[Row, Input]

def builder(rows: Seq[Row]): Table
}

abstract class AbstractTableParser[Table] extends TableParser[Table] {
Expand Down Expand Up @@ -89,7 +122,7 @@ abstract class StringTableParser[Table] extends AbstractTableParser[Table] {
def parseRows(xs: Strings, header: Header): Try[Table] = {
val rys = for (w <- xs) yield rowParser.parse(w)(header)
// System.err.println(s"StringTableParser: parsed ${rys.size} of ${xs.size} rows")
for (rs <- FP.sequence(if (forgiving) logFailures(rys) else rys)) yield builder(rs)
for (rs <- FP.sequence(if (forgiving) logFailures(rys) else rys)) yield builder(rs, Some(header))
}

}
Expand All @@ -105,6 +138,7 @@ abstract class StringsTableParser[Table] extends AbstractTableParser[Table] {
def parseRows(xs: Seq[Strings], header: Header): Try[Table] = {
val rys = for (w <- xs) yield rowParser.parse(w)(header)
// System.err.println(s"StringsTableParser: parsed ${rys.size} of ${xs.size} rows")
for (rs <- FP.sequence(if (forgiving) logFailures(rys) else rys)) yield builder(rs)
for (rs <- FP.sequence(if (forgiving) logFailures(rys) else rys)) yield builder(rs, Some(header))
}

}
32 changes: 4 additions & 28 deletions src/main/scala/com/phasmidsoftware/render/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package com.phasmidsoftware.render

import com.phasmidsoftware.table.Indexed
import org.joda.time.LocalDate

import scala.annotation.implicitNotFound
Expand All @@ -25,20 +24,18 @@ trait Renderer[T] {
*
* @param t the input parameter, i.e. the object to be rendered.
* @param attrs a map of attributes for this value of U.
* @tparam U the type of the result.
* @return a new instance of U.
*/
def render[U: TreeWriter](t: T, attrs: Map[String, String]): U = implicitly[TreeWriter[U]].node(style, Some(asString(t)), baseAttrs ++ attrs)
def render(t: T, attrs: Map[String, String]): Node = Node(style, Some(asString(t)), baseAttrs ++ attrs)

/**
* Render an instance of T as a U.
* This signature does not support the specification of attributes.
*
* @param t the input parameter, i.e. the object to be rendered.
* @tparam U the type of the result.
* @return a new instance of U.
*/
def render[U: TreeWriter](t: T): U = render(t, Map())
def render(t: T): Node = render(t, Map())

/**
* Method to render content as a String.
Expand Down Expand Up @@ -75,34 +72,13 @@ trait UntaggedRenderer[T] extends Renderer[T] {
abstract class TaggedRenderer[T](val style: String, override val baseAttrs: Map[String, String] = Map()) extends Renderer[T]

abstract class ProductRenderer[T <: Product : ClassTag](val style: String, override val baseAttrs: Map[String, String] = Map()) extends Renderer[T] {
override def render[U: TreeWriter](t: T, attrs: Map[String, String]): U = implicitly[TreeWriter[U]].node(style, attrs, us(t))
override def render(t: T, attrs: Map[String, String]): Node = Node(style, attrs, nodes(t))

protected def us[U: TreeWriter](t: T): Seq[U]
}

abstract class IndexedRenderer[T: Renderer](val style: String, indexStyle: String, override val baseAttrs: Map[String, String] = Map()) extends Renderer[Indexed[T]] {
/**
* Render an instance of Indexed[T] as a U.
*
* @param ti the input parameter, i.e. the object to be rendered.
* @param attrs a map of attributes for this value of U.
* @tparam U the type of the result.
* @return a new instance of U.
*/
override def render[U: TreeWriter](ti: Indexed[T], attrs: Map[String, String]): U = {
val indexRenderer: Renderer[Int] = new TaggedRenderer[Int](indexStyle) {}
implicitly[TreeWriter[U]].node(style, Seq(indexRenderer.render(ti.i), implicitly[Renderer[T]].render(ti.t)))
}
protected def nodes(t: T): Seq[Node]
}

object Renderer {

// NOTE: this is only used in unit tests
def render[T: Renderer, U: TreeWriter](t: T): U = implicitly[Renderer[T]].render(t)

// NOTE: this is only used in unit tests
def render[T: Renderer, U: TreeWriter](t: T, a: String): U = implicitly[Renderer[T]].render(t, Map("name" ->a))

trait StringRenderer extends UntaggedRenderer[String]

implicit object StringRenderer extends StringRenderer
Expand Down
Loading

0 comments on commit 96369fc

Please sign in to comment.