Skip to content

Latest commit

 

History

History
196 lines (129 loc) · 4.52 KB

README.md

File metadata and controls

196 lines (129 loc) · 4.52 KB

Smark

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

Usage

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")

Main Scala features used in Smark

Context Functions

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

Implicit conversions are used in two places:

  • Converting String to MdUnit
  • Converting String to Column

Converting String to MdUnit

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

Converting String to Column

Implicit conversion enables syntax like this:

table{ header("column1", "column2" : Left) }

what make the second column left aligned

| column1 | column2 |
| --- | :--- |

Union Types & Singleton Types

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 Aliases

Opaque type alias is used to differ Unit from MdUnit in order to provide safe implicit conversion from String.

String interpolation

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

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.

More examples