Skip to content

Commit df7d35d

Browse files
authored
convert public case classes in laika.rewrite.link (#490)
1 parent fc0a278 commit df7d35d

File tree

7 files changed

+208
-97
lines changed

7 files changed

+208
-97
lines changed

core/shared/src/main/scala/laika/directive/std/LinkDirectives.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private[laika] object LinkDirectives {
4545
private def linkConfig[T](cursor: DocumentCursor): Either[String, LinkConfig] =
4646
cursor.config
4747
.getOpt[LinkConfig]
48-
.map(_.getOrElse(LinkConfig()))
48+
.map(_.getOrElse(LinkConfig.empty))
4949
.leftMap(_.message)
5050

5151
/** Implementation of the `api` directive that creates links to API documentation based

core/shared/src/main/scala/laika/rewrite/TemplateRewriter.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ sealed abstract class OutputContext {
170170
object OutputContext {
171171

172172
private final case class Impl(fileSuffix: String, formatSelector: String)
173-
extends OutputContext
173+
extends OutputContext {
174+
override def productPrefix: String = "OutputContext"
175+
}
174176

175177
private[laika] def apply(fileSuffix: String, formatSelector: String): OutputContext =
176178
Impl(fileSuffix, formatSelector)

core/shared/src/main/scala/laika/rewrite/link/IconRegistry.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ import laika.config.{ ConfigEncoder, DefaultKey, LaikaKeys }
55

66
/** Registers Icon AST elements for use with the `@:icon` directive and the `IconReference` AST element.
77
*/
8-
case class IconRegistry private (icons: Map[String, Icon])
8+
class IconRegistry private (private val icons: Map[String, Icon]) {
9+
10+
def getIcon(id: String): Option[Icon] = icons.get(id)
11+
12+
}
913

1014
object IconRegistry {
1115

12-
def apply(icons: (String, Icon)*): IconRegistry = IconRegistry(icons.toMap)
16+
def apply(icons: (String, Icon)*): IconRegistry = new IconRegistry(icons.toMap)
1317

1418
implicit val encoder: ConfigEncoder[IconRegistry] = ConfigEncoder.map[Element].contramap(_.icons)
1519

core/shared/src/main/scala/laika/rewrite/link/LinkConfig.scala

Lines changed: 121 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,73 @@
1616

1717
package laika.rewrite.link
1818

19-
import laika.ast.{ Path, Target }
20-
import laika.config._
19+
import laika.ast.{ ExternalTarget, InternalTarget, Path, Target, VirtualPath }
20+
import laika.config.*
2121

22-
/** @author Jens Halm
23-
*/
24-
case class LinkConfig(
25-
targets: Seq[TargetDefinition] = Nil,
26-
apiLinks: Seq[ApiLinks] = Nil,
27-
sourceLinks: Seq[SourceLinks] = Nil
28-
)
22+
sealed abstract class LinkConfig {
23+
24+
/** List of global link definitions, mapping an identifier to an internal or external target.
25+
*
26+
* Allows to centralize commonly used URLs and associate them with an id that can be used
27+
* in markup sources, avoiding the repetitive definition of those URLs in the markup.
28+
*
29+
* The use of these ids in markup does not require a directive, it can be used with
30+
* "native" markup syntax, e.g. `[linkText][linkId]` in Markdown where `linkId` is
31+
* the identifier defined here.
32+
*/
33+
def targets: Seq[TargetDefinition]
34+
35+
/** Defines a list of base URLs for API links which allows the use
36+
* of the `@:api` directive in markup.
37+
*
38+
* Different base URLs can be defined for different packages,
39+
* so that fully qualified class names point to the correct external sources.
40+
* In markup it is then sufficient to pass the fully qualified class name
41+
* to the directive (e.g. `@:api(com.foo.Person)`)
42+
*/
43+
def apiLinks: Seq[ApiLinks]
44+
45+
/** Defines a list of base URLs for links to the sources of referenced classes
46+
* which allows the use of the `@:source` directive in markup.
47+
*
48+
* Different base URLs can be defined for different packages,
49+
* so that fully qualified class names or a relative path to a markup source file
50+
* point to the correct external sources.
51+
* In markup it is then sufficient to pass the fully qualified class name
52+
* to the directive (e.g. `@:source(com.foo.Person)`) or, when pointing to
53+
* markup sources, its relative path (e.g. `@:source(setup/intro.md)`)
54+
*/
55+
def sourceLinks: Seq[SourceLinks]
56+
57+
def addTargets(newTargets: TargetDefinition*): LinkConfig
58+
59+
def addApiLinks(newLinks: ApiLinks*): LinkConfig
60+
61+
def addSourceLinks(newLinks: SourceLinks*): LinkConfig
62+
63+
}
2964

3065
object LinkConfig {
3166

32-
val empty: LinkConfig = LinkConfig(Nil, Nil, Nil)
67+
private final case class Impl(
68+
targets: Seq[TargetDefinition],
69+
apiLinks: Seq[ApiLinks],
70+
sourceLinks: Seq[SourceLinks]
71+
) extends LinkConfig {
72+
73+
override def productPrefix: String = "LinkConfig"
74+
75+
def addTargets(newTargets: TargetDefinition*): LinkConfig =
76+
copy(targets = targets ++ newTargets)
77+
78+
def addApiLinks(newLinks: ApiLinks*): LinkConfig = copy(apiLinks = apiLinks ++ newLinks)
79+
80+
def addSourceLinks(newLinks: SourceLinks*): LinkConfig =
81+
copy(sourceLinks = sourceLinks ++ newLinks)
82+
83+
}
84+
85+
val empty: LinkConfig = Impl(Nil, Nil, Nil)
3386

3487
implicit val key: DefaultKey[LinkConfig] = DefaultKey(LaikaKeys.links)
3588

@@ -42,7 +95,7 @@ object LinkConfig {
4295
val mappedTargets = targets.map { case (id, targetURL) =>
4396
TargetDefinition(id, Target.parse(targetURL))
4497
}
45-
LinkConfig(mappedTargets.toSeq, apiLinks, sourceLinks)
98+
Impl(mappedTargets.toSeq, apiLinks, sourceLinks)
4699
}
47100
}
48101

@@ -111,19 +164,49 @@ object LinkValidation {
111164

112165
}
113166

114-
case class TargetDefinition(id: String, target: Target)
167+
sealed abstract class TargetDefinition {
168+
def id: String
169+
def target: Target
170+
}
171+
172+
object TargetDefinition {
173+
174+
private final case class Impl(id: String, target: Target) extends TargetDefinition {
175+
override def productPrefix: String = "TargetDefinition"
176+
}
177+
178+
private[link] def apply(id: String, target: Target): TargetDefinition = Impl(id, target)
115179

116-
case class SourceLinks(baseUri: String, suffix: String, packagePrefix: String = "*")
180+
def external(id: String, uri: String): TargetDefinition = Impl(id, ExternalTarget(uri))
181+
182+
def internal(id: String, path: VirtualPath): TargetDefinition = Impl(id, InternalTarget(path))
183+
}
184+
185+
sealed abstract class SourceLinks {
186+
def baseUri: String
187+
def suffix: String
188+
def packagePrefix: String
189+
190+
def withPackagePrefix(prefix: String): SourceLinks
191+
}
117192

118193
object SourceLinks {
119194

195+
private final case class Impl(baseUri: String, suffix: String, packagePrefix: String)
196+
extends SourceLinks {
197+
override def productPrefix: String = "SourceLinks"
198+
def withPackagePrefix(prefix: String): SourceLinks = copy(packagePrefix = prefix)
199+
}
200+
201+
def apply(baseUri: String, suffix: String): SourceLinks = Impl(baseUri, suffix, "*")
202+
120203
implicit val decoder: ConfigDecoder[SourceLinks] = ConfigDecoder.config.flatMap { config =>
121204
for {
122205
baseUri <- config.get[String]("baseUri")
123206
prefix <- config.get[String]("packagePrefix", "*")
124207
suffix <- config.get[String]("suffix")
125208
} yield {
126-
SourceLinks(baseUri, suffix, prefix)
209+
Impl(baseUri, suffix, prefix)
127210
}
128211
}
129212

@@ -137,21 +220,39 @@ object SourceLinks {
137220

138221
}
139222

140-
case class ApiLinks(
141-
baseUri: String,
142-
packagePrefix: String = "*",
143-
packageSummary: String = "index.html"
144-
)
223+
sealed abstract class ApiLinks {
224+
def baseUri: String
225+
def packagePrefix: String
226+
def packageSummary: String
227+
228+
def withPackagePrefix(prefix: String): ApiLinks
229+
def withPackageSummary(prefix: String): ApiLinks
230+
}
145231

146232
object ApiLinks {
147233

234+
private final case class Impl(
235+
baseUri: String,
236+
packagePrefix: String,
237+
packageSummary: String
238+
) extends ApiLinks {
239+
240+
override def productPrefix: String = "ApiLinks"
241+
242+
def withPackagePrefix(prefix: String): ApiLinks = copy(packagePrefix = prefix)
243+
244+
def withPackageSummary(summary: String): ApiLinks = copy(packageSummary = summary)
245+
}
246+
247+
def apply(baseUri: String): ApiLinks = Impl(baseUri, "*", "index.html")
248+
148249
implicit val decoder: ConfigDecoder[ApiLinks] = ConfigDecoder.config.flatMap { config =>
149250
for {
150251
baseUri <- config.get[String]("baseUri")
151252
prefix <- config.get[String]("packagePrefix", "*")
152253
summary <- config.get[String]("packageSummary", "index.html")
153254
} yield {
154-
ApiLinks(baseUri, prefix, summary)
255+
Impl(baseUri, prefix, summary)
155256
}
156257
}
157258

core/shared/src/test/scala/laika/config/ConfigCodecSpec.scala

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package laika.config
1818

1919
import cats.data.NonEmptyChain
20-
import laika.ast.{ DocumentMetadata, ExternalTarget, IconGlyph, IconStyle, InternalTarget }
20+
import laika.ast.{ DocumentMetadata, IconGlyph, IconStyle }
2121
import laika.ast.Path.Root
2222
import laika.ast.RelativePath.CurrentTree
2323
import laika.rewrite.{ Version, Versions }
@@ -176,23 +176,25 @@ class ConfigCodecSpec extends FunSuite {
176176

177177
object links {
178178

179-
def sort(config: LinkConfig): LinkConfig = config.copy(targets = config.targets.sortBy(_.id))
180-
181-
val fullyPopulatedInstance = LinkConfig(
182-
Seq(
183-
TargetDefinition("bar", InternalTarget(CurrentTree / "bar")),
184-
TargetDefinition("ext", ExternalTarget("http://ext.com")),
185-
TargetDefinition("foo", InternalTarget(CurrentTree / "foo"))
186-
),
187-
Seq(
188-
ApiLinks("https://foo.api/", "foo", "package.html"),
189-
ApiLinks("https://bar.api/", "foo.bar")
190-
),
191-
Seq(
192-
SourceLinks("https://foo.source/", "scala", "foo"),
193-
SourceLinks("https://bar.source/", "java", "foo.bar")
179+
def sort(config: LinkConfig): LinkConfig = LinkConfig.empty
180+
.addTargets(config.targets.sortBy(_.id) *)
181+
.addApiLinks(config.apiLinks *)
182+
.addSourceLinks(config.sourceLinks *)
183+
184+
val fullyPopulatedInstance = LinkConfig.empty
185+
.addTargets(
186+
TargetDefinition.internal("bar", CurrentTree / "bar"),
187+
TargetDefinition.external("ext", "http://ext.com"),
188+
TargetDefinition.internal("foo", CurrentTree / "foo")
189+
)
190+
.addApiLinks(
191+
ApiLinks("https://foo.api/").withPackagePrefix("foo").withPackageSummary("package.html"),
192+
ApiLinks("https://bar.api/").withPackagePrefix("foo.bar")
193+
)
194+
.addSourceLinks(
195+
SourceLinks("https://foo.source/", "scala").withPackagePrefix("foo"),
196+
SourceLinks("https://bar.source/", "java").withPackagePrefix("foo.bar")
194197
)
195-
)
196198

197199
}
198200

@@ -234,10 +236,9 @@ class ConfigCodecSpec extends FunSuite {
234236
""".stripMargin
235237
decode[LinkConfig](
236238
input,
237-
LinkConfig(
238-
targets = Seq(TargetDefinition("foo", InternalTarget(CurrentTree / "foo"))),
239-
apiLinks = Seq(ApiLinks("https://bar.api/"))
240-
),
239+
LinkConfig.empty
240+
.addTargets(TargetDefinition.internal("foo", CurrentTree / "foo"))
241+
.addApiLinks(ApiLinks("https://bar.api/")),
241242
links.sort
242243
)
243244
}

core/shared/src/test/scala/laika/rewrite/RewriteRulesSpec.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,12 @@ class RewriteRulesSpec extends FunSuite with ParagraphCompanionShortcuts with Te
317317

318318
private val linkTarget = RootElement(InternalLinkTarget(Id("ref")))
319319

320-
private val linkConfig = LinkConfig(targets =
321-
Seq(
322-
TargetDefinition("int", InternalTarget(RelativePath.parse("../doc-1.md#ref"))),
323-
TargetDefinition("ext", ExternalTarget("https://www.foo.com/")),
324-
TargetDefinition("inv", InternalTarget(RelativePath.parse("../doc-99.md#ref")))
320+
private val linkConfig = LinkConfig.empty
321+
.addTargets(
322+
TargetDefinition.internal("int", RelativePath.parse("../doc-1.md#ref")),
323+
TargetDefinition.external("ext", "https://www.foo.com/"),
324+
TargetDefinition.internal("inv", RelativePath.parse("../doc-99.md#ref"))
325325
)
326-
)
327326

328327
def run(ref: LinkIdReference, expected: Block): Unit = {
329328
val root =

0 commit comments

Comments
 (0)