Skip to content

Commit

Permalink
IDL (schema definition) AST rendering (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegIlyenko committed May 22, 2016
1 parent ea4e756 commit 2bebb3f
Show file tree
Hide file tree
Showing 3 changed files with 764 additions and 266 deletions.
8 changes: 4 additions & 4 deletions src/main/scala/sangria/parser/QueryParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ trait TypeSystemDefinitions { this: Parser with Tokens with Ignored with Directi
(comment, pos, name, args, locations) ast.DirectiveDefinition(name, args, locations, comment, Some(pos)))
}

def DirectiveLocations = rule { DirectiveLocation.+(ws('|')) ~> (_.toList) }
def DirectiveLocations = rule { DirectiveLocation.+(wsNoComment('|')) ~> (_.toList) }

def DirectiveLocation = rule { Comments ~ trackPos ~ Name ~> ((comment, pos, name) ast.DirectiveLocation(name, comment, Some(pos))) }

Expand Down Expand Up @@ -350,11 +350,11 @@ trait Types { this: Parser with Tokens with Ignored ⇒

def NamedType = rule { Ignored.* ~ trackPos ~ TypeName ~> ((pos, name) ast.NamedType(name, Some(pos)))}

def ListType = rule { trackPos ~ ws('[') ~ Type ~ ws(']') ~> ((pos, tpe) ast.ListType(tpe, Some(pos))) }
def ListType = rule { trackPos ~ ws('[') ~ Type ~ wsNoComment(']') ~> ((pos, tpe) ast.ListType(tpe, Some(pos))) }

def NonNullType = rule {
trackPos ~ TypeName ~ ws('!') ~> ((pos, name) ast.NotNullType(ast.NamedType(name, Some(pos)), Some(pos))) |
trackPos ~ ListType ~ ws('!') ~> ((pos, tpe) ast.NotNullType(tpe, Some(pos)))
trackPos ~ TypeName ~ wsNoComment('!') ~> ((pos, name) ast.NotNullType(ast.NamedType(name, Some(pos)), Some(pos))) |
trackPos ~ ListType ~ wsNoComment('!') ~> ((pos, tpe) ast.NotNullType(tpe, Some(pos)))
}
}

Expand Down
151 changes: 146 additions & 5 deletions src/main/scala/sangria/renderer/QueryRenderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object QueryRenderer {
separator = "",
mandatorySeparator = " ",
mandatoryLineBreak = " ",
definitionSeparator = "",
definitionSeparator = "\n",
inputFieldSeparator = ",",
inputListSeparator = ",",
formatInputValues = false,
Expand All @@ -41,9 +41,19 @@ object QueryRenderer {
(if (idx != 0 && shouldRenderComment(sel.comment, config)) config.lineBreak else "") + render(sel, config, indentLevel + 1)
} mkString config.mandatoryLineBreak

"{" + config.lineBreak + rendered + config.lineBreak + indent + "}"
"{" + config.lineBreak + rendered + config.lineBreak + indent + "}"
} else ""

def renderFieldDefinitions(fields: List[FieldDefinition], indent: String, indentLevel: Int, config: QueryRendererConfig) =
if (fields.nonEmpty) {
val rendered = fields.zipWithIndex map {
case (field, idx)
(if (idx != 0 && shouldRenderComment(field.comment, config)) config.lineBreak else "") + render(field, config, indentLevel + 1)
} mkString config.mandatoryLineBreak

"{" + config.lineBreak + rendered + config.lineBreak + indent + "}"
} else "{}"

def renderDirs(dirs: List[Directive], config: QueryRendererConfig, frontSep: Boolean = false, withSep: Boolean = true) =
(if (dirs.nonEmpty && frontSep && withSep) config.separator else "") +
(dirs map (render(_, config)) mkString config.separator) +
Expand All @@ -52,6 +62,34 @@ object QueryRenderer {
def renderArgs(args: List[Argument], config: QueryRendererConfig, withSep: Boolean = true) =
if (args.nonEmpty) "(" + (args map (render(_, config)) mkString ("," + config.separator)) + ")" + (if (withSep) config.separator else "") else ""

def renderInputValueDefs(args: List[InputValueDefinition], indentLevel: Int, config: QueryRendererConfig, withSep: Boolean = true) =
if (args.nonEmpty) {
val argsRendered = args.zipWithIndex map { case (a, idx)
(if (idx != 0 && shouldRenderComment(a.comment, config)) config.lineBreak else "") +
(if (shouldRenderComment(a.comment, config)) config.mandatoryLineBreak else if (idx != 0) config.separator else "") +
render(a, config, if (shouldRenderComment(a.comment, config)) indentLevel + 1 else 0)
}

"(" + (argsRendered mkString (",")) + ")" + (if (withSep) config.separator else "")
} else ""

def renderInputObjectFieldDefs(fields: List[InputValueDefinition], indentLevel: Int, config: QueryRendererConfig) = {
val fieldsRendered = fields.zipWithIndex map { case (f, idx)
(if (idx != 0 && shouldRenderComment(f.comment, config)) config.lineBreak else "") +
render(f, config, indentLevel + 1)
}

fieldsRendered mkString config.mandatoryLineBreak
}

def renderInterfaces(interfaces: List[NamedType], config: QueryRendererConfig, frontSep: Boolean = false, withSep: Boolean = true) =
if (interfaces.nonEmpty)
(if (frontSep) config.mandatorySeparator else "") +
"implements" + config.mandatorySeparator +
(interfaces map (render(_, config)) mkString ("," + config.separator)) +
(if (withSep) config.separator else "")
else ""

def renderOpType(operationType: OperationType) = operationType match {
case OperationType.Query "query"
case OperationType.Mutation "mutation"
Expand All @@ -63,7 +101,7 @@ object QueryRenderer {

def renderComment(comment: Option[Comment], indent: String, config: QueryRendererConfig): String =
if (shouldRenderComment(comment, config))
comment.get.lines map (l indent + "#" + (if (l.trim.nonEmpty) " " else "") + l.trim) mkString (
comment.get.lines map (l indent + "#" + (if (l.trim.nonEmpty && !l.trim.startsWith("#")) " " else "") + l.trim) mkString (
"",
config.mandatoryLineBreak,
config.mandatoryLineBreak)
Expand All @@ -73,7 +111,7 @@ object QueryRenderer {
def renderInputComment(comment: Option[Comment], indent: String, config: QueryRendererConfig) =
if (config.formatInputValues && shouldRenderComment(comment, config)) renderComment(comment, indent, config) + indent else ""

def render(node: AstNode, config: QueryRendererConfig = Pretty, indentLevel: Int = 0): String = {
def render(node: AstNode, config: QueryRendererConfig = Pretty, indentLevel: Int = 0, prefix: Option[String] = None): String = {
lazy val indent = config.indentLevel * indentLevel

node match {
Expand All @@ -93,7 +131,8 @@ object QueryRenderer {

case FragmentDefinition(name, typeCondition, dirs, sels, comment, _)
renderComment(comment, indent, config) +
indent + "fragment" + config.mandatorySeparator + name + config.mandatorySeparator + "on" + config.mandatorySeparator + typeCondition.name + config.separator +
indent + "fragment" + config.mandatorySeparator + name + config.mandatorySeparator + "on" +
config.mandatorySeparator + typeCondition.name + config.separator +
renderDirs(dirs, config) +
renderSelections(sels, indent, indentLevel, config)

Expand Down Expand Up @@ -174,6 +213,108 @@ object QueryRenderer {
(if (config.formatInputValues) renderComment(comment, indent, config) else "") +
indent + name + ":" + rendered
case c @ Comment(_, _) renderComment(Some(c), indent, config)

case ScalarTypeDefinition(name, dirs, comment, _)
renderComment(comment, indent, config) +
indent + "scalar" + config.mandatorySeparator + name +
renderDirs(dirs, config, frontSep = true)

case ObjectTypeDefinition(name, interfaces, fields, dirs, comment, _)
renderComment(comment, indent, config) +
indent + prefix.getOrElse("") + "type" + config.mandatorySeparator + name +
config.mandatorySeparator +
renderInterfaces(interfaces, config) +
renderDirs(dirs, config) +
renderFieldDefinitions(fields, indent, indentLevel, config)

case InputObjectTypeDefinition(name, fields, dirs, comment, _)
renderComment(comment, indent, config) +
indent + "input" + config.mandatorySeparator + name +
config.mandatorySeparator +
renderDirs(dirs, config) +
"{" + config.lineBreak +
renderInputObjectFieldDefs(fields, indentLevel, config) +
config.lineBreak +
indent + "}"

case InterfaceTypeDefinition(name, fields, dirs, comment, _)
renderComment(comment, indent, config) +
indent + "interface" + config.mandatorySeparator + name +
config.separator +
renderDirs(dirs, config) +
renderFieldDefinitions(fields, indent, indentLevel, config)

case UnionTypeDefinition(name, types, dirs, comment, _)
renderComment(comment, indent, config) +
indent + "union" + config.mandatorySeparator + name +
renderDirs(dirs, config, frontSep = true) +
config.separator + "=" + config.separator +
(types map(render(_, config)) mkString (config.separator + "|" + config.separator))

case EnumTypeDefinition(name, values, dirs, comment, _)
val renderedValues = values.zipWithIndex map { case (value, idx)
(if (idx != 0 && shouldRenderComment(value.comment, config)) config.lineBreak else "") +
render(value, config, indentLevel + 1)
} mkString config.mandatoryLineBreak

renderComment(comment, indent, config) +
indent + "enum" + config.mandatorySeparator + name +
config.separator +
renderDirs(dirs, config) +
"{" + config.lineBreak + renderedValues + config.lineBreak + indent + "}"

case EnumValueDefinition(name, dirs, comment, _)
renderComment(comment, indent, config) +
indent + name +
renderDirs(dirs, config, frontSep = true)

case FieldDefinition(name, fieldType, args, dirs, comment, _)
renderComment(comment, indent, config) +
indent + name +
renderInputValueDefs(args, indentLevel, config, withSep = false) +
":" + config.separator + render(fieldType) +
renderDirs(dirs, config, frontSep = true)

case InputValueDefinition(name, valueType, default, dirs, comment, _)
renderComment(comment, indent, config) +
indent + name + ":" + config.separator + render(valueType, config) +
default.fold("")(d config.separator + "=" + config.separator + render(d, config)) +
renderDirs(dirs, config, frontSep = true)

case TypeExtensionDefinition(definition, comment, _)
renderComment(comment, indent, config) +
render(definition.copy(comment = None), config, indentLevel, Some("extend" + config.mandatorySeparator))

case DirectiveDefinition(name, args, locations, comment, _)
val locsRendered = locations.zipWithIndex map { case (l, idx)
(if (idx != 0 && shouldRenderComment(l.comment, config)) config.lineBreak else "") +
(if (shouldRenderComment(l.comment, config)) config.lineBreak else if (idx != 0) config.separator else "") +
render(l, config, if (shouldRenderComment(l.comment, config)) indentLevel + 1 else 0)
}

renderComment(comment, indent, config) +
indent + "directive" + config.separator + "@" + name +
renderInputValueDefs(args, indentLevel, config) +
"on" + (if (shouldRenderComment(locations.head.comment, config)) "" else config.mandatorySeparator) +
locsRendered.mkString(config.separator + "|")

case DirectiveLocation(name, comment, _)
renderComment(comment, indent, config) + indent + name

case SchemaDefinition(ops, dirs, comment, _)
val renderedOps = ops.zipWithIndex map { case (op, idx)
(if (idx != 0 && shouldRenderComment(op.comment, config)) config.lineBreak else "") +
render(op, config, indentLevel + 1)
} mkString config.mandatoryLineBreak

renderComment(comment, indent, config) +
indent + "schema" + config.separator +
renderDirs(dirs, config) +
"{" + config.lineBreak + renderedOps + config.lineBreak + indent + "}"

case OperationTypeDefinition(op, tpe, comment, _)
renderComment(comment, indent, config) +
indent + renderOpType(op) + ":" + config.separator + render(tpe, config)
}
}

Expand Down
Loading

0 comments on commit 2bebb3f

Please sign in to comment.