Skip to content

Commit

Permalink
Merge pull request #531 from typelevel/api/adt-companions
Browse files Browse the repository at this point in the history
preparing repackaging for 1.0 - move ADT members to companion + some more removals from public API
  • Loading branch information
jenshalm authored Sep 15, 2023
2 parents a9f903a + 4f0fa34 commit 2449cb7
Show file tree
Hide file tree
Showing 105 changed files with 653 additions and 581 deletions.
15 changes: 10 additions & 5 deletions core/shared/src/main/scala/laika/api/MarkupParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ package laika.api

import cats.syntax.all.*
import laika.api.builder.{ OperationConfig, ParserBuilder }
import laika.api.errors.{ InvalidDocument, ParserError }
import laika.ast.Path.Root
import laika.ast.{ Document, EmbeddedConfigValue, Path, RewritePhase, UnresolvedDocument }
import laika.config.Origin.DocumentScope
import laika.config.{ Config, ConfigBuilder, ConfigValue, Origin }
import laika.factory.MarkupFormat
import laika.parse.markup.DocumentParser
import laika.parse.markup.DocumentParser.{ DocumentInput, InvalidDocument, ParserError }
import laika.parse.markup.DocumentParser.DocumentInput

/** Performs a parse operation from text markup to a
* document tree without a subsequent render operation.
Expand Down Expand Up @@ -92,24 +93,28 @@ class MarkupParser private[laika] (val format: MarkupFormat, val config: Operati
}

def rewritePhase(doc: Document, phase: RewritePhase): Either[ParserError, Document] = for {
rules <- config.rewriteRulesFor(doc, phase).leftMap(ParserError(_, doc.path))
result <- doc.rewrite(rules).leftMap(ParserError.apply(_, doc.path))
rules <- config.rewriteRulesFor(doc, phase).leftMap(ParserError(_))
result <- doc.rewrite(rules).leftMap(ParserError(_))
} yield result

def asParserError(document: InvalidDocument): ParserError = new ParserError(
s"One or more error nodes in result:\n${InvalidDocument.format(document)}".trim
)

def rewriteDocument(resolvedDoc: Document): Either[ParserError, Document] = for {
phase1 <- rewritePhase(resolvedDoc, RewritePhase.Build)
phase2 <- rewritePhase(phase1, RewritePhase.Resolve)
result <- InvalidDocument
.from(phase2, config.failOnMessages)
.map(ParserError(_))
.map(asParserError)
.toLeft(phase2)
} yield result

for {
unresolved <- docParser(input)
resolvedConfig <- unresolved.config
.resolve(Origin(DocumentScope, input.path), config.baseConfig)
.left.map(ParserError(_, input.path))
.left.map(ParserError(_))
resolvedDoc = resolveDocument(unresolved, resolvedConfig)
result <- rewriteDocument(resolvedDoc)
} yield result
Expand Down
8 changes: 4 additions & 4 deletions core/shared/src/main/scala/laika/api/Renderer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ package laika.api

import cats.syntax.all.*
import laika.api.builder.{ OperationConfig, RendererBuilder, TwoPhaseRendererBuilder }
import laika.api.errors.RendererError
import laika.ast.Path.Root
import laika.ast.*
import laika.factory.{ MarkupFormat, RenderContext, RenderFormat, TwoPhaseRenderFormat }
import laika.parse.markup.DocumentParser.RendererError
import laika.render.Formatter.Indentation
import laika.rewrite.OutputContext
import laika.rewrite.nav.{ NoOpPathTranslator, PathTranslator }
import laika.rewrite.nav.PathTranslator

/** Performs a render operation from a document AST to a target format
* as a string. The document AST may be obtained by a preceding parse
Expand Down Expand Up @@ -70,7 +70,7 @@ abstract class Renderer private[laika] (val config: OperationConfig, skipRewrite
}
)

private val defaultPathTranslator: PathTranslator = NoOpPathTranslator
private val defaultPathTranslator: PathTranslator = PathTranslator.noOp

/** Renders the specified document as a String.
*/
Expand Down Expand Up @@ -133,7 +133,7 @@ abstract class Renderer private[laika] (val config: OperationConfig, skipRewrite
config
.rewriteRulesFor(doc, RewritePhase.Render(OutputContext(format)))
.map(_.rewriteElement(targetElement))
.leftMap(RendererError(_, doc.path))
.leftMap(RendererError(_))
}

(if (skipRewrite) Right(targetElement) else rewrite).map { elementToRender =>
Expand Down
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/laika/api/Transformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
package laika.api

import laika.api.builder.{ OperationConfig, TransformerBuilder, TwoPhaseTransformerBuilder }
import laika.api.errors.TransformationError
import laika.ast.Path
import laika.ast.Path.Root
import laika.factory.{ MarkupFormat, RenderFormat, TwoPhaseRenderFormat }
import laika.parse.markup.DocumentParser.TransformationError

/** Performs a transformation from text markup like Markdown or reStructuredText
* to a target format like HTML as a String.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
package laika.api.builder

import laika.ast.RewriteRules.RewriteRulesBuilder
import laika.config.{ Config, ConfigBuilder, ConfigEncoder, DefaultKey, Key, ValidationError }
import laika.ast._
import laika.config.{ Config, ConfigBuilder, ConfigEncoder, DefaultKey, Key }
import laika.config.ConfigError.ValidationError
import laika.ast.*
import laika.bundle.ExtensionBundle.PathTranslatorExtensionContext
import laika.bundle.{
BundleOrigin,
Expand Down
129 changes: 129 additions & 0 deletions core/shared/src/main/scala/laika/api/errors/errors.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package laika.api.errors

import cats.syntax.all.*
import cats.data.{ Chain, NonEmptyChain }
import laika.ast.{ Document, DocumentTreeRoot, Invalid, MessageFilter, Path }
import laika.config.ConfigError
import laika.config.ConfigError.TreeConfigErrors

sealed trait TransformationError {
def message: String
}

case class RendererError(message: String) extends TransformationError {
override def toString: String = message
}

object RendererError {

def apply(message: String): RendererError =
new RendererError(s"Rendering Error: $message")

def apply(configError: ConfigError): RendererError =
RendererError(s"Configuration Error: ${configError.message}")

}

case class ParserError(message: String) extends TransformationError {
override def toString: String = message
}

object ParserError {

def apply(message: String): ParserError =
new ParserError(s"Error parsing input: $message")

def apply(configError: ConfigError): ParserError =
ParserError(s"Configuration Error: ${configError.message}")

}

private[laika] case class InvalidDocument(
errors: Either[NonEmptyChain[ConfigError], NonEmptyChain[Invalid]],
path: Path
) extends RuntimeException(
s"One or more errors processing document '$path': ${InvalidDocument.format(errors, path)}"
)

private[laika] object InvalidDocument {

def apply(path: Path, error: ConfigError, errors: ConfigError*): InvalidDocument =
new InvalidDocument(Left(NonEmptyChain.fromChainPrepend(error, Chain.fromSeq(errors))), path)

def apply(path: Path, error: Invalid, errors: Invalid*): InvalidDocument =
new InvalidDocument(Right(NonEmptyChain.fromChainPrepend(error, Chain.fromSeq(errors))), path)

def indent(lineContent: String): String = {
val lines = lineContent.split('\n')
lines.head + "\n " + lines.last
}

def format(
errors: Either[NonEmptyChain[ConfigError], NonEmptyChain[Invalid]],
path: Path
): String =
errors.fold(
configErrors => configErrors.map(_.message).mkString_("\n"),
invalidElems => invalidElems.map(InvalidDocument.formatElement(path)).toList.mkString
)

def format(doc: InvalidDocument): String = format(doc.errors, doc.path)

def formatElement(docPath: Path)(element: Invalid): String = {
val pathStr = element.source.path.fold("") { srcPath =>
if (srcPath == docPath) "" else srcPath.toString + ":"
}
s""" [$pathStr${element.source.position.line}]: ${element.message.content}
|
| ${indent(element.source.position.lineContentWithCaret)}
|
|""".stripMargin
}

def from(document: Document, failOn: MessageFilter): Option[InvalidDocument] = {
val invalidElements = document.invalidElements(failOn)
NonEmptyChain.fromSeq(invalidElements).map(inv => InvalidDocument(Right(inv), document.path))
}

}

private[laika] case class InvalidDocuments(documents: NonEmptyChain[InvalidDocument])
extends RuntimeException(
s"One or more invalid documents:\n${InvalidDocuments.format(documents)}"
)

private[laika] object InvalidDocuments {

def format(documents: NonEmptyChain[InvalidDocument]): String = {

def formatDoc(doc: InvalidDocument): String =
s"""${doc.path}
|
|${InvalidDocument.format(doc)}""".stripMargin

documents.map(formatDoc).mkString_("").trim
}

def from(
result: Either[TreeConfigErrors, DocumentTreeRoot],
failOn: MessageFilter
): Either[InvalidDocuments, DocumentTreeRoot] = {
result.fold(
errors =>
Left(
InvalidDocuments(
errors.failures.map(err => InvalidDocument(Left(err.failures), err.path))
)
),
root => from(root, failOn).toLeft(root)
)
}

def from(root: DocumentTreeRoot, failOn: MessageFilter): Option[InvalidDocuments] = {
val invalidDocs = root.allDocuments
.flatMap(InvalidDocument.from(_, failOn))
NonEmptyChain.fromSeq(invalidDocs)
.map(InvalidDocuments(_))
}

}
13 changes: 2 additions & 11 deletions core/shared/src/main/scala/laika/ast/Cursor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,8 @@ import laika.ast.Path.Root
import laika.ast.RewriteRules.RewriteRulesBuilder
import laika.collection.TransitionalCollectionOps.*
import laika.config.Config.ConfigResult
import laika.config.{
Config,
ConfigEncoder,
ConfigError,
ConfigValue,
DocumentConfigErrors,
Key,
LaikaKeys,
Origin,
TreeConfigErrors
}
import laika.config.ConfigError.{ DocumentConfigErrors, TreeConfigErrors }
import laika.config.{ Config, ConfigEncoder, ConfigError, ConfigValue, Key, LaikaKeys, Origin }
import laika.parse.SourceFragment
import laika.rewrite.{ OutputContext, ReferenceResolver }
import laika.rewrite.link.{ LinkConfig, LinkValidation, LinkValidator, TargetValidation }
Expand Down
2 changes: 1 addition & 1 deletion core/shared/src/main/scala/laika/ast/RewriteRules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package laika.ast
import cats.syntax.all.*
import laika.ast.RewriteRules.{ ChainedRewriteRules, RewritePhaseBuilder, RewriteRulesBuilder }
import laika.config.Config.ConfigResult
import laika.config.ConfigErrors
import laika.config.ConfigError.ConfigErrors
import laika.factory.{ RenderFormat, TwoPhaseRenderFormat }
import laika.rewrite.{ OutputContext, TemplateFormatter, UnresolvedNodeDetector }
import laika.rewrite.link.LinkResolver
Expand Down
1 change: 1 addition & 0 deletions core/shared/src/main/scala/laika/ast/documents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import laika.ast.Path.Root
import laika.ast.RelativePath.CurrentTree
import laika.ast.RewriteRules.RewriteRulesBuilder
import laika.config.Config.IncludeMap
import laika.config.ConfigError.TreeConfigErrors
import laika.config.*
import laika.rewrite.nav.{ AutonumberConfig, TargetFormats }
import laika.rewrite.{ DefaultTemplatePath, OutputContext, TemplateRewriter }
Expand Down
3 changes: 2 additions & 1 deletion core/shared/src/main/scala/laika/ast/links.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
package laika.ast

import laika.ast
import laika.config.{ ASTValue, ConfigValue, LaikaKeys }
import laika.config.{ ConfigValue, LaikaKeys }
import laika.config.ConfigValue.ASTValue
import laika.parse.SourceFragment

/** An internal or external link target that can be referenced by id, usually only part of the raw document tree and then
Expand Down
23 changes: 13 additions & 10 deletions core/shared/src/main/scala/laika/ast/resolvers.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package laika.ast

import laika.config.{ ASTValue, ConfigError, ConfigValue, InvalidType, Key, SimpleConfigValue }
import laika.parse.SourceFragment
import laika.config.{ ConfigError, ConfigValue, Key }
import ConfigError.InvalidType
import laika.config.ConfigValue.{ ASTValue, SimpleValue }

/** Represents a placeholder inline element that needs
* to be resolved in a rewrite step.
Expand All @@ -14,6 +15,8 @@ trait SpanResolver extends Span with Unresolved {
def runsIn(phase: RewritePhase): Boolean
}

import laika.parse.SourceFragment

/** Represents a placeholder block element that needs to be resolved in a rewrite step.
* Useful for elements that need access to the document, structure, title
* or configuration before being fully resolved.
Expand Down Expand Up @@ -124,7 +127,7 @@ case class TemplateContextReference(
case Right(Some(ASTValue(s: TemplateSpan))) => s
case Right(Some(ASTValue(RootElement(content, _)))) => EmbeddedRoot(content)
case Right(Some(ASTValue(e: Element))) => TemplateElement(e)
case Right(Some(simple: SimpleConfigValue)) => TemplateString(simple.render)
case Right(Some(simple: SimpleValue)) => TemplateString(simple.render)
case Right(None) if !required => TemplateString("")
case Right(None) => TemplateElement(missing)
case Right(Some(unsupported)) => TemplateElement(invalidType(unsupported))
Expand All @@ -150,13 +153,13 @@ case class MarkupContextReference(
type Self = MarkupContextReference

def resolve(cursor: DocumentCursor): Span = cursor.resolveReference(ref) match {
case Right(Some(ASTValue(s: Span))) => s
case Right(Some(ASTValue(e: Element))) => TemplateElement(e)
case Right(Some(simple: SimpleConfigValue)) => Text(simple.render)
case Right(None) if !required => Text("")
case Right(None) => missing
case Right(Some(unsupported)) => invalidType(unsupported)
case Left(configError) => invalid(configError)
case Right(Some(ASTValue(s: Span))) => s
case Right(Some(ASTValue(e: Element))) => TemplateElement(e)
case Right(Some(simple: SimpleValue)) => Text(simple.render)
case Right(None) if !required => Text("")
case Right(None) => missing
case Right(Some(unsupported)) => invalidType(unsupported)
case Left(configError) => invalid(configError)
}

def withOptions(options: Options): MarkupContextReference = copy(options = options)
Expand Down
5 changes: 2 additions & 3 deletions core/shared/src/main/scala/laika/bundle/ParserBundle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@

package laika.bundle

import laika.ast._
import laika.ast.*
import laika.parse.Parser
import laika.parse.markup.DocumentParser.DocumentInput

/** Bundles a collection of all types of parsers used in a transformation.
*
Expand Down Expand Up @@ -87,7 +86,7 @@ class ParserBundle(
class ParserHooks(
val postProcessBlocks: Seq[Block] => Seq[Block] = identity,
val postProcessDocument: UnresolvedDocument => UnresolvedDocument = identity,
val preProcessInput: DocumentInput => DocumentInput = identity
val preProcessInput: String => String = identity
) {

/** Merges this instance with the specified base.
Expand Down
4 changes: 4 additions & 0 deletions core/shared/src/main/scala/laika/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
package laika.config

import laika.config.Config.ConfigResult
import laika.config.ConfigError.{ DecodingError, NotFound }
import laika.config.ConfigValue.{ ArrayValue, ObjectValue }
import laika.parse.hocon.{ IncludeResource, ObjectBuilderValue }

import scala.annotation.tailrec
import scala.util.Try

/** API for retrieving configuration values based on a string key and a decoder.
Expand Down Expand Up @@ -190,6 +193,7 @@ private[laika] class ObjectConfig(
}
}

@tailrec
private def lookup(keySegments: Seq[String], target: ObjectValue): Option[Field] = {
(target.values.find(_.key == keySegments.head), keySegments.tail) match {
case (res, Nil) => res
Expand Down
2 changes: 2 additions & 0 deletions core/shared/src/main/scala/laika/config/ConfigBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package laika.config

import ConfigValue.ObjectValue

import laika.collection.TransitionalCollectionOps._

/** A builder for creating a Config instance programmatically.
Expand Down
Loading

0 comments on commit 2449cb7

Please sign in to comment.