diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3326a58..bea2ccc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,11 +89,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p toolkit/js/target target toolkit/native/target .js/target toolkit/jvm/target .jvm/target .native/target project/target + run: mkdir -p toolkit/js/target target toolkit/native/target .js/target site/target toolkit/jvm/target .jvm/target .native/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar toolkit/js/target target toolkit/native/target .js/target toolkit/jvm/target .jvm/target .native/target project/target + run: tar cf targets.tar toolkit/js/target target toolkit/native/target .js/target site/target toolkit/jvm/target .jvm/target .native/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') @@ -293,3 +293,50 @@ jobs: - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 + + site: + name: Generate Site + strategy: + matrix: + os: [ubuntu-latest] + scala: [3.2.2] + java: [temurin@17] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download Java (temurin@17) + id: download-java-temurin-17 + if: matrix.java == 'temurin@17' + uses: typelevel/download-java@v2 + with: + distribution: temurin + java-version: 17 + + - name: Setup Java (temurin@17) + id: setup-java-temurin-17 + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v3 + with: + distribution: jdkfile + java-version: 17 + jdkFile: ${{ steps.download-java-temurin-17.outputs.jdkFile }} + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' + run: sbt '++ ${{ matrix.scala }}' reload +update + + - name: Generate site + run: sbt '++ ${{ matrix.scala }}' docs/tlSite + + - name: Publish site + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + uses: peaceiris/actions-gh-pages@v3.9.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: site/target/docs/site + keep_files: true diff --git a/.mergify.yml b/.mergify.yml index 8b48885..f8575e2 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -21,6 +21,14 @@ pull_request_rules: - status-success=Build and Test (ubuntu-latest, 3, temurin@17, rootNative) actions: merge: {} +- name: Label site PRs + conditions: + - files~=^site/ + actions: + label: + add: + - site + remove: [] - name: Label toolkit PRs conditions: - files~=^toolkit/ diff --git a/README.md b/README.md index d7fd7cd..6c21a37 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ It currently includes: * [Cats] and [Cats Effect] * [FS2] and [FS2 I/O] +* [FS2 Data Csv] and its generic module * [http4s Ember client] * [Circe] and http4s integration * [Decline Effect] @@ -25,6 +26,7 @@ It currently includes: [Cats Effect]: https://typelevel.org/cats-effect [FS2]: https://fs2.io/#/ [FS2 I/O]: https://fs2.io/#/io +[FS2 Data Csv]: https://fs2-data.gnieh.org/documentation/csv/ [http4s Ember Client]: https://http4s.org/v0.23/docs/client.html [Circe]: https://circe.github.io/circe/ [Decline Effect]: https://ben.kirw.in/decline/effect.html diff --git a/build.sbt b/build.sbt index 8b1f31a..7ceceb3 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,9 @@ +import laika.helium.config._ +import laika.rewrite.nav.{ChoiceConfig, Selections, SelectionConfig} + ThisBuild / tlBaseVersion := "0.0" ThisBuild / startYear := Some(2023) - +ThisBuild / tlSitePublishBranch := Some("main") ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17")) ThisBuild / mergifyStewardConfig ~= { _.map(_.copy(author = "typelevel-steward[bot]")) @@ -26,3 +29,44 @@ lazy val toolkit = crossProject(JVMPlatform, JSPlatform, NativePlatform) ), mimaPreviousArtifacts := Set() ) + +lazy val docs = project + .in(file("site")) + .enablePlugins(TypelevelSitePlugin) + .dependsOn(toolkit.jvm) + .settings( + scalaVersion := "3.2.2", + tlSiteHelium ~= { + _.site.mainNavigation( + appendLinks = List( + ThemeNavigationSection( + "Related Projects", + TextLink.external("https://github.com/typelevel/fs2", "fs2"), + TextLink.external("https://github.com/typelevel/cats", "Cats"), + TextLink.external("https://github.com/circe/circe", "Circe"), + TextLink.external("https://github.com/http4s/http4s", "Http4s"), + TextLink.external("https://github.com/bkirwi/decline", "Decline"), + TextLink.external( + "https://github.com/typelevel/cats-effect", + "Cats Effect" + ), + TextLink.external( + "https://github.com/typelevel/munit-cats-effect", + "Munit Cats Effect" + ) + ) + ) + ) + }, + laikaConfig ~= { + _.withConfigValue( + Selections( + SelectionConfig( + "scala-version", + ChoiceConfig("scala-3", "Scala 3"), + ChoiceConfig("scala-2", "Scala 2") + ) + ) + ) + } + ) diff --git a/docs/directory.conf b/docs/directory.conf new file mode 100644 index 0000000..9a91efb --- /dev/null +++ b/docs/directory.conf @@ -0,0 +1,4 @@ +laika.navigationOrder = [ + index.md + examples.md +] \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..82362fd --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,172 @@ +# Examples + +This page contains examples of how typelevel-toolkit and [Scala CLI] work together to write single file scripts using all the power of the Typelevel's libraries. + +## POSTing data and writing the response to a file +This example was written by [Koroeskohr] and taken from the [Virtuslab Blog](https://virtuslab.com/blog/scala-toolkit-makes-scala-powerful-straight-out-of-the-box/). + +@:select(scala-version) + +@:choice(scala-3) + +```scala mdoc:silent +//> using lib "org.typelevel::toolkit::@VERSION@" + +import cats.effect.* +import io.circe.Decoder +import fs2.Stream +import fs2.io.file.* +import org.http4s.ember.client.* +import org.http4s.* +import org.http4s.implicits.* +import org.http4s.circe.* + +object Main extends IOApp.Simple: + case class Data(value: String) + given Decoder[Data] = Decoder.forProduct1("data")(Data.apply) + given EntityDecoder[IO, Data] = jsonOf[IO, Data] + + def run = EmberClientBuilder.default[IO].build.use { client => + val request: Request[IO] = + Request(Method.POST, uri"https://httpbin.org/anything") + .withEntity("file.txt bunchofdata") + + client + .expect[Data](request) + .map(_.value.split(" ")) + .flatMap { case Array(fileName, content) => + IO.println(s"Writing data to $fileName") *> + Stream(content) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(Path(fileName))) + .compile + .drain + } + } +``` +@:choice(scala-2) + +```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::0.0.2" + +import cats.effect._ +import io.circe.Decoder +import fs2.Stream +import fs2.io.file._ +import org.http4s.ember.client._ +import org.http4s._ +import org.http4s.implicits._ +import org.http4s.circe._ + +object Main extends IOApp.Simple { + case class Data(value: String) + implicit val json: Decoder[Data] = Decoder.forProduct1("data")(Data.apply) + implicit val enc: EntityDecoder[IO, Data] = jsonOf[IO, Data] + + def run = EmberClientBuilder.default[IO].build.use { client => + val request: Request[IO] = + Request(Method.POST, uri"https://httpbin.org/anything") + .withEntity("file.txt bunchofdata") + + client + .expect[Data](request) + .map(_.value.split(" ")) + .flatMap { case Array(fileName, content) => + IO.println(s"Writing data to $fileName") *> + Stream(content) + .through(fs2.text.utf8.encode) + .through(Files[IO].writeAll(Path(fileName))) + .compile + .drain + } + } +} +``` + +@:@ + +## Command line version of mkString + +In this example, [fs2] is used to read a stream of newline delimited strings from standard input and to reconcatenate them with comma by default, while [decline] is leveraged to parse the command line options. + +Compiling this example with [scala-native], adding these directives + +```scala mdoc:reset:silent +//> using packaging.output "mkString" +//> using platform "native" +//> using nativeMode "release-fast" +``` + +will produce a native executable that can be used in a similar way as the Scala's standard library `.mkString`: + +```sh +$ echo -e "foo\nbar" | ./mkString --prefix "[" -d "," --suffix "]" +// [foo,bar] +``` + +@:select(scala-version) + +@:choice(scala-3) +```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::@VERSION@" + +import cats.effect.* +import cats.syntax.all.* +import com.monovore.decline.* +import com.monovore.decline.effect.* +import fs2.* +import fs2.io.* + +val prefix = Opts.option[String]("prefix", "").withDefault("") +val delimiter = Opts.option[String]("delimiter", "", "d").withDefault(",") +val suffix = Opts.option[String]("suffix", "The suffix").withDefault("") + +val stringStream: Stream[IO, String] = stdinUtf8[IO](1024 * 1024 * 10) + .repartition(s => Chunk.array(s.split("\n", -1))) + .filter(_.nonEmpty) + +// inspired by list.mkString +object Main extends CommandIOApp("mkString", "Concatenates strings from stdin"): + def main = (prefix, delimiter, suffix).mapN { (pre, delim, post) => + val stream = Stream(pre) ++ stringStream.intersperse(delim) ++ Stream(post) + stream.foreach(IO.print).compile.drain.as(ExitCode.Success) + } +``` + +@:choice(scala-2) + +```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::0.0.2" + +import cats.effect._ +import cats.syntax.all._ +import com.monovore.decline._ +import com.monovore.decline.effect._ +import fs2._ +import fs2.io._ + +// inspired by list.mkString +object Main extends CommandIOApp("mkString", "Concatenates strings from stdin") { + val prefix = Opts.option[String]("prefix", "").withDefault("") + val delimiter = Opts.option[String]("delimiter", "", "d").withDefault(",") + val suffix = Opts.option[String]("suffix", "The suffix").withDefault("") + + val stringStream: Stream[IO, String] = stdinUtf8[IO](1024 * 1024 * 10) + .repartition(s => Chunk.array(s.split("\n", -1))) + .filter(_.nonEmpty) + + def main = (prefix, delimiter, suffix).mapN { (pre, delim, post) => + val stream = Stream(pre) ++ stringStream.intersperse(delim) ++ Stream(post) + stream.foreach(IO.print).compile.drain.as(ExitCode.Success) + } +} +``` + +@:@ + + +[fs2]: https://fs2.io/#/ +[decline]: https://ben.kirw.in/decline/ +[scala-native]: https://scala-native.org/en/stable/ +[Scala CLI]: https://scala-cli.virtuslab.org/ +[Koroeskohr]: https://github.com/Koroeskohr \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..ebfda99 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,66 @@ +# typelevel-toolkit + +A toolkit of **great libraries** to start building **Typelevel** apps on JVM, Node.js, and Native! + +Our very own flavour of the [Scala Toolkit]. + +## Overview + +Typelevel toolkit is a meta library that currently includes these libraries: + +- [Cats] and [Cats Effect] +- [fs2] and [fs2 I/O] +- [fs2 data csv] and its generic module +- [Http4s Ember client] +- [Circe] and http4s integration +- [Decline Effect] +- [Munit Cats Effect] + +and it's published for Scala 2.12, 2.13 and 3.2.2. + +To use it with [Scala CLI] use this directive: +```scala +//> using lib "org.typelevel::toolkit::@VERSION@" +``` + +Albeit being created to be used with [Scala CLI], typelevel-toolkit can be imported into your `build.sbt` using: +```scala +libraryDependencies += "org.typelevel" %% "toolkit" % "@VERSION@" +// for native and js +libraryDependencies += "org.typelevel" %%% "toolkit" % "@VERSION@" +``` + +## Quick Start Example +@:select(scala-version) +@:choice(scala-3) +```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::@VERSION@" + +import cats.effect.* + +object Hello extends IOApp.Simple: + def run = IO.println("Hello toolkit!") +``` +@:choice(scala-2) +```scala mdoc:reset:silent +//> using lib "org.typelevel::toolkit::@VERSION@" + +import cats.effect._ + +object Hello extends IOApp.Simple { + def run = IO.println("Hello toolkit!") +} +``` +@:@ + +[Scala CLI]: https://scala-cli.virtuslab.org/ +[Scala Toolkit]: https://github.com/VirtusLab/toolkit +[Cats]: https://typelevel.org/cats +[Cats Effect]: https://typelevel.org/cats-effect +[fs2]: https://fs2.io/#/ +[fs2 I/O]: https://fs2.io/#/io +[fs2 data csv]: https://fs2-data.gnieh.org/documentation/csv/ +[Http4s Ember Client]: https://http4s.org/v0.23/docs/client.html +[Circe]: https://circe.github.io/circe/ +[Decline Effect]: https://ben.kirw.in/decline/effect.html +[Munit Cats Effect]: https://github.com/typelevel/munit-cats-effect \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index e8536b8..2281840 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,7 @@ val sbtTlVersion = "0.5.0-M9" addSbtPlugin("org.typelevel" % "sbt-typelevel" % sbtTlVersion) addSbtPlugin("org.typelevel" % "sbt-typelevel-mergify" % sbtTlVersion) +addSbtPlugin("org.typelevel" % "sbt-typelevel-site" % sbtTlVersion) addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.10") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0")