Smark is a markdown generation library in typesafe way
Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3
Add the following to your build.sbt:
libraryDependencies += "halotukozak" %% "smark" % "0.2.0"
Then you can use Smark in your Scala 3 project:
import halotukozak.smark.typography.*
markdown {
heading[1]("Smark")
paragraph {
text[Normal]("Smark is a markdown generation library in typesafe way")
quote[Important]("Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3")
}
}
generates:
# Smark
Smark is a markdown generation library in typesafe way
> [!Important]
> Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3
If you prefer to skip brackets, you can use the following syntax:
import halotukozak.smark.typography.*
markdown:
heading[1]("Smark")
paragraph:
text[Normal]("Smark is a markdown generation library in typesafe way")
quote[Important]("Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3")
def table(init: Table ?=> MdUnit)(using m: MdElement): MdUnit
takes a parameter which operates on the Table instance what enables a syntax like this:
table {
header("column1", "column2")
row {
cell("row1column1")
cell("row1column2")
}
row {
cell("row2column1")
cell("row2column2")
}
}
Implicit conversions are used in two places:
- Converting String to MdUnit
- Converting String to Column
Implicit conversion is defined in the companion object of MdElement:
given Conversion[String, MdUnit] = MdElement.fromString
This allows to use String in the context of MdUnit. For example:
paragraph {
"This is a paragraph"
}
also allows to use raw String in place of `text[Normal]
//noinspection ScalaUnusedExpression
paragraph {
"Conversion to MdUnit" : MdUnit
"Last expression do not have to be converted"
}
as you can see, the //noinspection ScalaUnusedExpression
is needed to suppress the warning about unused expression for IntelliJ IDEA
Implicit conversion enables syntax like this:
table{ header("column1", "column2" : Left) }
what make the second column left aligned
| column1 | column2 |
| --- | :--- |
Union types are used with Singleton types to define the heading levels
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
what makes this code not compilable
heading[7]("This will not compile")
Opaque type alias is used to differ Unit from MdUnit in order to provide safe implicit conversion from String.
String interpolation is used to create code blocks in markdown. It also enables IDE support. For example:
code(scala"val x = 1")
generates:
```scala
val x = 1
```
Macros are used to generate markdown from the code. Mostly used inline ones but also type pattern matching, quotation and splicing. For example:
inline def textMacro[Style <: TextStyle](inline inner: String): String = ${textImpl[Style]('{ inner })}
def textImpl[Style <: TextStyle : Type](inner: Expr[String])(using Quotes): Expr[String] = {
Type.of[Style] match {
case '[Normal] => inner
case '[Bold & Italic] => '{ "***" + $inner + "***" }
case '[Bold] => '{ "**" + $inner + "**" }
case '[Italic] => '{ "*" + $inner + "*" }
(...)
}
}
is used to generate text with different styles during compilation.