diff --git a/README.md b/README.md index c4249a6..facc540 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ Smark is a markdown generation library in typesafe way -dupa - Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3 ## Usage @@ -32,7 +30,7 @@ heading[1]("Smark") } ``` -will generate: +generates: ``` # Smark @@ -69,22 +67,125 @@ def table(init: Table ?=> MdUnit)(using m: MdElement): MdUnit takes a parameter which operates on the Table instance what enables a syntax like this: ```scala - - |table { - | header("column1", "column2") - | row { - | cell("row1column1") - | cell("row1column2") - | } - | row { - | cell("row2column1") - | cell("row2column2") - | } - |} - | +table { + header("column1", "column2") + row { + cell("row1column1") + cell("row1column2") + } + row { + cell("row2column1") + cell("row2column2") + } +} ``` ### Implicit Conversions -[Implicit conversions](https://docs.scala-lang.org/scala3/book/ca-implicit-conversions.html) \ No newline at end of file +[Implicit conversions](https://docs.scala-lang.org/scala3/book/ca-implicit-conversions.html) 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: + +```scala +given Conversion[String, MdUnit] = MdElement.fromString +``` + +This allows to use String in the context of MdUnit. For example: + +```scala +paragraph { + "This is a paragraph" +} +``` + +also allows to use raw String in place of `text[Normal] + +```scala +//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: + +```scala +table{ header("column1", "column2" : Left) } +``` + +what make the second column left aligned + +``` +| column1 | column2 | +| --- | :--- | +``` + +### Union Types & Singleton Types + + +[Union types](https://docs.scala-lang.org/scala3/book/types-union.html) are used with [Singleton types](https://docs.scala-lang.org/sips/42.type.html#:~:text=A%20singleton%20type%20is%20of,for%20which%20v%20eq%20p%20) to define the heading levels + +```scala +type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6 +``` + +what makes this code not compilable + +```scala +heading[7]("This will not compile") +``` + +### Opaque Type Aliases + + +[Opaque type alias](https://docs.scala-lang.org/scala3/reference/other-new-features/opaques.html) is used to differ Unit from MdUnit in order to provide safe implicit conversion from String. + +### String interpolation + + +[String interpolation](https://www.scala-lang.org/api/current/scala/StringContext.html) is used to create code blocks in markdown. It also enables IDE support. For example: + +```scala +code(scala"val x = 1") +``` + +generates: + +```` +```scala +val x = 1 +``` +```` + +### Macros + + +[Macros](https://docs.scala-lang.org/scala3/guides/macros/macros.html) are used to generate markdown from the code. Mostly used inline ones but also type pattern matching, quotation and splicing. For example: + +```scala +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. \ No newline at end of file diff --git a/src/test/scala/integration/Readme.scala b/src/test/scala/integration/Readme.scala index 13c1876..43e21f1 100644 --- a/src/test/scala/integration/Readme.scala +++ b/src/test/scala/integration/Readme.scala @@ -5,124 +5,191 @@ import typography.* import _root_.java.io.FileWriter import halotukozak.smark.given_Conversion_String_MdUnit - +import halotukozak.smark.tables.* +import halotukozak.smark.tables.given_Conversion_String_Left @main -def generateReadme(): Unit = +private def generateReadme(): Unit = val file = new FileWriter("README.md") try file.write(content) finally file.close() //noinspection ScalaUnusedExpression -val content: String = - markdown { - comment("Do not edit this file manually, it is generated by Smark") - heading[1]("Smark") - paragraph { - "Smark is a markdown generation library in typesafe way": MdUnit - "Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3" - } - heading[2]("Usage") - paragraph { - "Add the following to your build.sbt:": MdUnit - code { - scala"""libraryDependencies += "halotukozak" %% "smark" % "0.1.0-SNAPSHOT"""" - } - "Then you can use Smark in your Scala 3 project:": MdUnit - code { - scala"""|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") - | } - |}""".stripMargin - } - "will generate:": MdUnit - text[BlockCode] { - 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") - } - } - } +val content: String = markdown { + comment("Do not edit this file manually, it is generated by Smark") + heading[1]("Smark") + paragraph { + "Smark is a markdown generation library in typesafe way": MdUnit + "Do not take this project seriously, first of all I wanted to learn some mechanisms from Scala 3" + } + heading[2]("Usage") + paragraph { + "Add the following to your build.sbt:": MdUnit + code { + scala"""libraryDependencies += "halotukozak" %% "smark" % "0.1.0-SNAPSHOT"""" } - "If you prefer to skip brackets, you can use the following syntax:": MdUnit + "Then you can use Smark in your Scala 3 project:": MdUnit code { - scala"""|import halotukozak.smark.typography.* + scala"""| + |import halotukozak.smark.typography.* | - |markdown: - | heading[1]("Smark") - | paragraph: + |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") + | } + |} |""".stripMargin } - heading[2]("Main Scala features used in Smark") - heading[3]("Context Functions") + "generates:": MdUnit + text[BlockCode] { + 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") + } + } + } + } + "If you prefer to skip brackets, you can use the following syntax:": MdUnit + code { + scala"""| + |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") + | + |""".stripMargin + } + heading[2]("Main Scala features used in Smark") + heading[3]("Context Functions") + paragraph { + string.link["Context functions"]("https://docs.scala-lang.org/scala3/reference/contextual/context-functions.html") + " are used to create a DSL. For example table function:" + code { + scala"def table(init: Table ?=> MdUnit)(using m: MdElement): MdUnit".stripMargin + } + "takes a parameter which operates on the Table instance what enables a syntax like this:": MdUnit + code { + scala"""| + |table { + | header("column1", "column2") + | row { + | cell("row1column1") + | cell("row1column2") + | } + | row { + | cell("row2column1") + | cell("row2column2") + | } + |} + |""".stripMargin + } + } + heading[3]("Implicit Conversions") + paragraph { + string.link["Implicit conversions"]("https://docs.scala-lang.org/scala3/book/ca-implicit-conversions.html") + " are used in two places:": MdUnit + list[Asterisk]( + "Converting String to MdUnit", + "Converting String to Column" + ) + heading[4]("Converting String to MdUnit") paragraph { - string.link["Context functions"]("https://docs.scala-lang.org/scala3/reference/contextual/context-functions.html") + " are used to create a DSL. For example table function:" + "Implicit conversion is defined in the companion object of MdElement:": MdUnit code { - scala"def table(init: Table ?=> MdUnit)(using m: MdElement): MdUnit".stripMargin + scala"""given Conversion[String, MdUnit] = MdElement.fromString""" } - "takes a parameter which operates on the Table instance what enables a syntax like this:": MdUnit + "This allows to use String in the context of MdUnit. For example:": MdUnit code { scala"""| - |table { - | header("column1", "column2") - | row { - | cell("row1column1") - | cell("row1column2") - | } - | row { - | cell("row2column1") - | cell("row2column2") - | } + |paragraph { + | "This is a paragraph" |} - |""" + |""".stripMargin } + "also allows to use raw String in place of `text[Normal]": MdUnit + code { + scala"""|//noinspection ScalaUnusedExpression + |paragraph { + | "Conversion to MdUnit" : MdUnit + | "Last expression do not have to be converted" + |} + |""".stripMargin + } + "as you can see, the `//noinspection ScalaUnusedExpression` is needed to suppress the warning about unused expression for IntelliJ IDEA" } - heading[3]("Implicit Conversions") + heading[4]("Converting String to Column") paragraph { - string.link["Implicit conversions"]("https://docs.scala-lang.org/scala3/book/ca-implicit-conversions.html") + " are used in two places:": MdUnit - list[Asterisk]( - "Converting String to MdUnit", - "Converting String to Column" - ) - heading[4]("Converting String to MdUnit") - paragraph { - "Implicit conversion is defined in the companion object of MdElement:": MdUnit - code { - scala"""given Conversion[String, MdUnit] = MdElement.fromString""" - } - "This allows to use String in the context of MdUnit. For example:": MdUnit - code { - scala"""|paragraph { - | "This is a paragraph" - |}""".stripMargin - } - "also allows to use raw String in place of `text[Normal]": MdUnit - code { - scala"""|//noinspection ScalaUnusedExpression - |paragraph { - | "Conversion to MdUnit" : MdUnit - | "Last expression do not have to be converted" - |}""".stripMargin - } - "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:": MdUnit + code { + scala"""table{ header("column1", "column2" : Left) }""" } - heading[4]("Converting String to Column") - paragraph { - "Implicit conversion enables syntax like this:": MdUnit - code { - scala"""header("column1", "column2" : Left)""" + "what make the second column left aligned": MdUnit + text[BlockCode] { + markdown { + table { + header("column1", "column2": Left) + } } - "what make the second column left aligned" } } } + heading[3]("Union Types & Singleton Types") + paragraph { + string.link["Union types"]("https://docs.scala-lang.org/scala3/book/types-union.html") + + " are used with " + + string.link["Singleton types"]("https://docs.scala-lang.org/sips/42.type.html#:~:text=A%20singleton%20type%20is%20of,for%20which%20v%20eq%20p%20") + + " to define the heading levels": MdUnit + code { + scala"""type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6""" + } + "what makes this code not compilable": MdUnit + code { + scala"""heading[7]("This will not compile")""" + } + } + heading[3]("Opaque Type Aliases") + paragraph { + string.link["Opaque type alias"]("https://docs.scala-lang.org/scala3/reference/other-new-features/opaques.html") + + " is used to differ Unit from MdUnit in order to provide safe implicit conversion from String.": MdUnit + } + heading[3]("String interpolation") + paragraph { + string.link["String interpolation"]("https://www.scala-lang.org/api/current/scala/StringContext.html") + + " is used to create code blocks in markdown. It also enables IDE support. For example:": MdUnit + code { + scala"""code(scala"val x = 1")""" + } + "generates:": MdUnit + "````\n" + // nested code block is not supported yet + markdown { + code(scala"val x = 1") + } + "\n````" + } + heading[3]("Macros") + paragraph { + string.link["Macros"]("https://docs.scala-lang.org/scala3/guides/macros/macros.html") + + " are used to generate markdown from the code. Mostly used inline ones but also type pattern matching, quotation and splicing. For example:": MdUnit + code { + scala"""| + |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 + "*" } + | (...) + | } + |} + |""".stripMargin + } + "is used to generate text with different styles during compilation.": MdUnit + } + +}