diff --git a/.github/workflows/ghpages.yml b/.github/workflows/ghpages.yml index 1991d10..1c27ac4 100644 --- a/.github/workflows/ghpages.yml +++ b/.github/workflows/ghpages.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 21 - name: Build run: ./mill -i docs.hepek - name: Deploy diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb5fb5c..ab73795 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,8 @@ on: env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} PGP_SECRET: ${{ secrets.PGP_SECRET }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + MILL_SONATYPE_PASSWORD: ${{ secrets.MILL_SONATYPE_PASSWORD }} + MILL_SONATYPE_USERNAME: ${{ secrets.MILL_SONATYPE_USERNAME }} jobs: tests: @@ -30,10 +30,8 @@ jobs: passphrase: ${{ secrets.PGP_PASSPHRASE }} - name: Publish run: > - ./mill -i mill.scalalib.PublishModule/publishAll \ + ./mill -i mill.scalalib.SonatypeCentralPublishModule/publishAll \ --gpgArgs --passphrase=$PGP_PASSPHRASE,--no-tty,--pinentry-mode,loopback,--batch,--yes,-a,-b \ --publishArtifacts __.publishArtifacts \ --readTimeout 600000 \ - --awaitTimeout 600000 \ - --release true \ - --signed true + --awaitTimeout 600000 diff --git a/.mill-version b/.mill-version index dd97386..dfa546b 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.12.11 \ No newline at end of file +0.12.14 \ No newline at end of file diff --git a/DEV.md b/DEV.md index 47ba742..9fa0163 100644 --- a/DEV.md +++ b/DEV.md @@ -18,7 +18,7 @@ scala-cli compile examples\scala-cli ```sh # RELEASE -$VERSION="0.10.0" +$VERSION="0.11.0" git commit --allow-empty -m "Release $VERSION" git tag -a $VERSION -m "Release $VERSION" git push --atomic origin main --tags diff --git a/build.mill b/build.mill index 294ae73..4c5e246 100644 --- a/build.mill +++ b/build.mill @@ -6,13 +6,14 @@ import $ivy.`ba.sake::mill-hepek::0.1.0` import mill._ import mill.scalalib._, scalajslib._, scalanativelib._ import mill.scalalib.publish._ +import mill.scalalib.SonatypeCentralPublishModule import de.tobiasroeser.mill.vcs.version.VcsVersion import ba.sake.millhepek.MillHepekModule object V { val tupson = "0.13.0" val scalatags = "0.13.1" - val hepek = "0.30.0" + val hepek = "0.33.0" } object `sharaf-core` extends Module { @@ -21,6 +22,7 @@ object `sharaf-core` extends Module { } object native extends SharafCoreModule with ScalaNativeCommonModule { def moduleDeps = Seq(querson.native, formson.native, validson.native) + object test extends ScalaNativeTests with SharafTestModule } trait SharafCoreModule extends SharafPublishModule with PlatformScalaModule { def artifactName = "sharaf-core" @@ -63,10 +65,21 @@ object `sharaf-helidon` extends SharafPublishModule { } } + +object `sharaf-snunit` extends ScalaNativeCommonModule with SharafPublishModule { + def artifactName = "sharaf-snunit" + def ivyDeps = super.ivyDeps() ++ Agg( + ivy"com.github.lolgab::snunit::0.10.3" + ) + def moduleDeps = Seq(`sharaf-core`.native) +} + object querson extends Module { object jvm extends QuersonModule with ScalaJvmCommonModule object js extends QuersonModule with ScalaJSCommonModule - object native extends QuersonModule with ScalaNativeCommonModule + object native extends QuersonModule with ScalaNativeCommonModule { + object test extends ScalaNativeTests with SharafTestModule + } trait QuersonModule extends SharafPublishModule with PlatformScalaModule { def artifactName = "querson" def pomSettings = super.pomSettings().copy(description = "Sharaf query params library") @@ -79,7 +92,9 @@ object querson extends Module { object formson extends Module { object jvm extends FormsonModule with ScalaJvmCommonModule //object js extends FormsonModule with ScalaJSCommonModule // java.nio.Path not supported - object native extends FormsonModule with ScalaNativeCommonModule + object native extends FormsonModule with ScalaNativeCommonModule { + object test extends ScalaNativeTests with SharafTestModule + } trait FormsonModule extends SharafPublishModule with PlatformScalaModule { def artifactName = "formson" def pomSettings = super.pomSettings().copy(description = "Sharaf form binding library") @@ -92,7 +107,9 @@ object formson extends Module { object validson extends Module { object jvm extends ValidsonModule with ScalaJvmCommonModule object js extends ValidsonModule with ScalaJSCommonModule - object native extends ValidsonModule with ScalaNativeCommonModule + object native extends ValidsonModule with ScalaNativeCommonModule { + object test extends ScalaNativeTests with SharafTestModule + } trait ValidsonModule extends SharafPublishModule with PlatformScalaModule { def artifactName = "validson" def ivyDeps = super.ivyDeps() ++ Agg( @@ -102,7 +119,7 @@ object validson extends Module { } } -trait SharafPublishModule extends SharafCommonModule with PublishModule { +trait SharafPublishModule extends SharafCommonModule with SonatypeCentralPublishModule { def publishVersion = VcsVersion.vcsState().format() def pomSettings = PomSettings( organization = "ba.sake", @@ -143,7 +160,7 @@ trait ScalaNativeCommonModule extends ScalaNativeModule { def ivyDeps = super.ivyDeps() ++ Agg( ivy"io.github.cquiroz::scala-java-time::2.6.0" ) - object test extends ScalaNativeTests with SharafTestModule + } trait SharafTestModule extends TestModule.Munit { @@ -189,6 +206,9 @@ object examples extends mill.Module { ) } } + object snunit extends SharafExampleModule with ScalaNativeCommonModule { + def moduleDeps = Seq(`sharaf-snunit`) + } } //////////////////// docs diff --git a/docs/resources/public/images/favicon.png b/docs/resources/public/images/favicon.png new file mode 100644 index 0000000..8276da4 Binary files /dev/null and b/docs/resources/public/images/favicon.png differ diff --git a/docs/resources/public/images/logo_original.png b/docs/resources/public/images/logo_original.png new file mode 100644 index 0000000..4bf7bd3 Binary files /dev/null and b/docs/resources/public/images/logo_original.png differ diff --git a/docs/resources/public/scripts/main.js b/docs/resources/public/scripts/main.js index 1ed84b3..4e518cd 100644 --- a/docs/resources/public/scripts/main.js +++ b/docs/resources/public/scripts/main.js @@ -1,7 +1,5 @@ -// set anchorjs stuff -var parent = "section"; for (i = 1; i <= 6; i++) { - // CSS selectors "section h1", "section h2" ... - anchors.add(parent + ' h' + i); -} + // CSS selectors "h1", "h2" ... + anchors.add('h' + i); +} \ No newline at end of file diff --git a/docs/resources/public/styles/main.css b/docs/resources/public/styles/main.css index 9089bff..00d53f7 100644 --- a/docs/resources/public/styles/main.css +++ b/docs/resources/public/styles/main.css @@ -1,24 +1,30 @@ -body { - font-size: 17px; - padding-top: 5rem; - /* coz navbar */ - margin-bottom: 55px; -} -.affix { - width: 100%; +.flex-centered { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; } -.navbar-brand { - display: flex; - align-items: center; - gap: 1rem; +/* default SVG maximum height */ +object[type="image/svg+xml"] { + max-height: 300px; + width: 100%; } -.navbar-brand img { - width: 24px; -} -.site-map p { - margin-bottom: 0; +@media (min-width: 768px) { + .blog-post { + display: grid; + grid-template-areas: "left-menu main-content"; + grid-template-columns: 300px 1fr; + } + .left-menu { + grid-area: left-menu; + padding: 1rem; + } + .main-content { + grid-area: main-content; + padding: 1rem; + } } \ No newline at end of file diff --git a/docs/src/files/Index.scala b/docs/src/files/Index.scala index 7c38adc..5d3f3f0 100644 --- a/docs/src/files/Index.scala +++ b/docs/src/files/Index.scala @@ -1,39 +1,33 @@ package files +import scalatags.Text.all.* import ba.sake.hepek.html.statik.BlogPostPage import utils.* -import Bundle.*, Tags.* object Index extends DocStaticPage { override def pageSettings = super.pageSettings .withTitle(Consts.ProjectName) - override def navbar = Some(Navbar) - - override def pageContent = frag( - maybeNavbar.map(n => div(n)), + override def mainContent = div( - Grid.row( - h1(Consts.ProjectName), - s""" - ${Consts.ProjectName} is a minimalistic Scala 3 web framework. - - Jump right into: - - [Tutorials](${files.tutorials.Index.ref}) to get you started - - [How-Tos](${files.howtos.Index.ref}) to get answers for some common questions - - [Reference](${files.reference.Index.ref}) to see detailed information - - [Philosophy](${files.philosophy.Index.ref}) to get insights into design decisions - - --- - Site map: - """.md, - div(cls := "site-map")( - siteMap.md - ) + h1(Consts.ProjectName), + s""" + ${Consts.ProjectName} is a minimalistic Scala 3 web framework. + + Jump right into: + - [Tutorials](${files.tutorials.Index.ref}) to get you started + - [How-Tos](${files.howtos.Index.ref}) to get answers for some common questions + - [Reference](${files.reference.Index.ref}) to see detailed information + - [Philosophy](${files.philosophy.Index.ref}) to get insights into design decisions + + --- + Site map: + """.md, + div(cls := "site-map")( + siteMap.md ) ) - ) private def siteMap = Index.staticSiteSettings.mainPages diff --git a/docs/src/files/SearchIndex.scala b/docs/src/files/SearchIndex.scala new file mode 100644 index 0000000..8d94120 --- /dev/null +++ b/docs/src/files/SearchIndex.scala @@ -0,0 +1,12 @@ +package files + +import ba.sake.hepek.fusejs.FusejsIndex +import ba.sake.hepek.html.statik.StaticPage +import utils.Consts + +object SearchIndex extends FusejsIndex { + + override def indexedPages: Seq[StaticPage] = + Consts.allSearchIndexedPages + +} diff --git a/docs/src/files/SearchResults.scala b/docs/src/files/SearchResults.scala new file mode 100644 index 0000000..9309c4a --- /dev/null +++ b/docs/src/files/SearchResults.scala @@ -0,0 +1,45 @@ +package files + +import ba.sake.hepek.html.PageSettings +import ba.sake.tupson.toJson +import scalatags.Text.all.* + +object SearchResults extends utils.DocStaticPage { + + override def pageSettings: PageSettings = super.pageSettings + .withTitle("Search results") + .withLabel("Search results") + + override def mainContent = div( + h1("Search results"), + div(id := "search-results-content")( + p("Loading...") + ) + ) + + private lazy val fuseList = SearchIndex.fusejsIndexedPagesData.toJson + + override def scriptsInline: List[String] = super.scriptsInline ++ List(s""" + import Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@7.1.0/dist/fuse.mjs' + + const urlParams = new URLSearchParams(window.location.search); + const qParam = urlParams.get('q'); + + const fuseIndex = await fetch('${SearchIndex.ref}').then(r => r.json()) + const myIndex = Fuse.parseIndex(fuseIndex); + const fuse = new Fuse(${fuseList}, { + keys: [ "title", "text" ] + }, myIndex); + + const searchRes = fuse.search(qParam); + console.log(JSON.stringify(searchRes)); + const searchResultsContentElem = document.getElementById("search-results-content"); + searchResultsContentElem.innerHTML = searchRes.map(r => { + const page = r.item; + return `
+

$${page.title}

+

$${page.text}

+
`; + }).join(""); + """) +} diff --git a/docs/src/files/howtos/CORS.scala b/docs/src/files/howtos/CORS.scala index 93a9ce8..36eb7f1 100644 --- a/docs/src/files/howtos/CORS.scala +++ b/docs/src/files/howtos/CORS.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object CORS extends HowToPage { @@ -21,10 +21,10 @@ object CORS extends HowToPage { use the `withCorsSettings` method and set desired config: ```scala import ba.sake.sharaf.handlers.cors.CorsSettings - import ba.sake.sharaf.*, routing.* + import ba.sake.sharaf.* val corsSettings = CorsSettings.default.withAllowedOrigins(Set("https://example.com")) - SharafHandler(routes).withCorsSettings(corsSettings)... + UndertowSharafServer(routes).withCorsSettings(corsSettings)... ``` """.md ) diff --git a/docs/src/files/howtos/ExceptionHandler.scala b/docs/src/files/howtos/ExceptionHandler.scala index 93936ce..f0982da 100644 --- a/docs/src/files/howtos/ExceptionHandler.scala +++ b/docs/src/files/howtos/ExceptionHandler.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object ExceptionHandler extends HowToPage { @@ -15,16 +15,16 @@ object ExceptionHandler extends HowToPage { "How to customize the Exception handler?", s""" - Use the `withExceptionMapper` on `SharafHandler`: + Use the `withExceptionMapper` on `UndertowSharafServer`: ```scala val customExceptionMapper: ExceptionMapper = { case e: MyException => val errorPage = MyErrorPage(e.getMessage()) Response.withBody(errorPage) - .withStatus(StatusCodes.INTERNAL_SERVER_ERROR) + .withStatus(StatusCode.InternalServerError) } val finalExceptionMapper = customExceptionMapper.orElse(ExceptionMapper.default) - val httpHandler = SharafHandler(routes) + val server = UndertowSharafServer(routes) .withExceptionMapper(finalExceptionMapper) ``` diff --git a/docs/src/files/howtos/ExternalConfig.scala b/docs/src/files/howtos/ExternalConfig.scala index 4651dd7..78b8ed8 100644 --- a/docs/src/files/howtos/ExternalConfig.scala +++ b/docs/src/files/howtos/ExternalConfig.scala @@ -1,7 +1,6 @@ package files.howtos -import utils.Consts -import utils.Bundle.* +import utils.* object ExternalConfig extends HowToPage { diff --git a/docs/src/files/howtos/HowToPage.scala b/docs/src/files/howtos/HowToPage.scala index 793b15b..cdcd395 100644 --- a/docs/src/files/howtos/HowToPage.scala +++ b/docs/src/files/howtos/HowToPage.scala @@ -1,9 +1,8 @@ package files.howtos import utils.* -import Bundle.* -trait HowToPage extends DocPage { +trait HowToPage extends utils.DocPage { override def categoryPosts = List( @@ -21,5 +20,5 @@ trait HowToPage extends DocPage { override def pageCategory = Some("How-Tos") - override def navbar = Some(Navbar.withActiveUrl(Index.ref)) + override def currentCategoryPage = Some(Index) } diff --git a/docs/src/files/howtos/Index.scala b/docs/src/files/howtos/Index.scala index fbea058..27c8e5f 100644 --- a/docs/src/files/howtos/Index.scala +++ b/docs/src/files/howtos/Index.scala @@ -1,7 +1,6 @@ package files.howtos -import utils.Bundle.* -import utils.Consts +import utils.* object Index extends HowToPage { diff --git a/docs/src/files/howtos/NotFound.scala b/docs/src/files/howtos/NotFound.scala index ed1f82f..a8ffa30 100644 --- a/docs/src/files/howtos/NotFound.scala +++ b/docs/src/files/howtos/NotFound.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object NotFound extends HowToPage { @@ -15,11 +15,11 @@ object NotFound extends HowToPage { "How to customize 404 NotFound handler?", s""" - Use the `withNotFoundHandler` on `SharafHandler`: + Use the `withNotFoundHandler` on `UndertowSharafServer`: ```scala - SharafHandler(routes).withNotFoundHandler { req => + UndertowSharafServer(routes).withNotFoundHandler { req => Response.withBody(MyCustomNotFoundPage) - .withStatus(StatusCodes.NOT_FOUND) + .withStatus(StatusCode.NotFound) } ``` diff --git a/docs/src/files/howtos/QueryParams.scala b/docs/src/files/howtos/QueryParams.scala index 41c0e2f..82a3e36 100644 --- a/docs/src/files/howtos/QueryParams.scala +++ b/docs/src/files/howtos/QueryParams.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object QueryParams extends HowToPage { @@ -87,7 +87,7 @@ object QueryParams extends HowToPage { val customSection = Section( "How to bind a custom query parameter?", - s""" + """ When you want to handle a custom *scalar* value in query params, you need to implement a `QueryStringRW[T]` instance manually: ```scala @@ -103,7 +103,7 @@ object QueryParams extends HowToPage { } private def typeError(path: String, tpe: String, value: Any): Nothing = - throw ParsingException(ParseError(path, s"invalid $$tpe", Some(value))) + throw ParsingException(ParseError(path, s"invalid \$tpe", Some(value))) ``` Then you can use it: diff --git a/docs/src/files/howtos/Redirect.scala b/docs/src/files/howtos/Redirect.scala index 1ce4869..9964822 100644 --- a/docs/src/files/howtos/Redirect.scala +++ b/docs/src/files/howtos/Redirect.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object Redirect extends HowToPage { diff --git a/docs/src/files/howtos/ResponseBody.scala b/docs/src/files/howtos/ResponseBody.scala index dda92b3..f98ca1e 100644 --- a/docs/src/files/howtos/ResponseBody.scala +++ b/docs/src/files/howtos/ResponseBody.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object ResponseBody extends HowToPage { @@ -23,7 +23,7 @@ object ResponseBody extends HowToPage { override def write(value: MyXML, exchange: HttpServerExchange): Unit = exchange.getResponseSender.send(value.asString) override def headers(value: String): Seq[(HttpString, Seq[String])] = Seq( - Headers.CONTENT_TYPE -> Seq("text/xml") + HttpString(HeaderNames.ContentType) -> Seq("text/xml") ) } ``` diff --git a/docs/src/files/howtos/Routes.scala b/docs/src/files/howtos/Routes.scala index 65a15b3..85b4da0 100644 --- a/docs/src/files/howtos/Routes.scala +++ b/docs/src/files/howtos/Routes.scala @@ -1,6 +1,6 @@ package files.howtos -import utils.Bundle.* +import utils.* object Routes extends HowToPage { @@ -69,7 +69,7 @@ object Routes extends HowToPage { val enumPathSection = Section( "How to bind path parameter as an enum?", - s""" + """ Sharaf needs a `FromPathParam[T]` instance for the `param[T]` extractor. It can automatically derive an instance for singleton enums: @@ -80,7 +80,7 @@ object Routes extends HowToPage { val routes = Routes: case GET -> Path("pricing", param[Cloud](cloud)) => - Response.withBody(s"cloud = $${cloud}") + Response.withBody(s"cloud = \${cloud}") ``` """.md @@ -88,14 +88,14 @@ object Routes extends HowToPage { val regexPathSection = Section( "How to bind path parameter as a regex?", - s""" + """ ```scala val userIdRegex = "user_id_(\\d+)".r val routes = Routes: case GET -> Path("pricing", userIdRegex(userId)) => - Response.withBody(s"userId = $${userId}") + Response.withBody(s"userId = \${userId}") ``` Note that the `userId` is bound as a `String`. @@ -111,7 +111,7 @@ object Routes extends HowToPage { val customPathSection = Section( "How to bind a custom path parameter?", - s""" + """ Sharaf needs a `FromPathParam[T]` instance available: ```scala import ba.sake.sharaf.routing.* @@ -123,7 +123,7 @@ object Routes extends HowToPage { val routes = Routes: case GET -> Path("pricing", param[MyType](myType)) => - Response.withBody(s"myType = $${myType}") + Response.withBody(s"myType = \${myType}") ``` """.md ) @@ -150,7 +150,7 @@ object Routes extends HowToPage { override def routes: Routes = Routes: case ... - val handler = SharafHandler( + val server = UndertowSharafServer( new MyController1, new MyController2 ) ``` diff --git a/docs/src/files/howtos/UploadFile.scala b/docs/src/files/howtos/UploadFile.scala index 338fbbe..3d09384 100644 --- a/docs/src/files/howtos/UploadFile.scala +++ b/docs/src/files/howtos/UploadFile.scala @@ -1,7 +1,6 @@ package files.howtos -import utils.Bundle.* -import utils.Consts +import utils.* object UploadFile extends HowToPage { diff --git a/docs/src/files/philosophy/Alternatives.scala b/docs/src/files/philosophy/Alternatives.scala index d80d4e8..5bfb0e8 100644 --- a/docs/src/files/philosophy/Alternatives.scala +++ b/docs/src/files/philosophy/Alternatives.scala @@ -1,6 +1,6 @@ package files.philosophy -import utils.Bundle.* +import utils.* object Alternatives extends PhilosophyPage { diff --git a/docs/src/files/philosophy/Authentication.scala b/docs/src/files/philosophy/Authentication.scala index 5b0a907..201c4cf 100644 --- a/docs/src/files/philosophy/Authentication.scala +++ b/docs/src/files/philosophy/Authentication.scala @@ -1,6 +1,6 @@ package files.philosophy -import utils.Bundle.* +import utils.* object Authentication extends PhilosophyPage { @@ -52,7 +52,7 @@ object Authentication extends PhilosophyPage { val denyByDefaultSection = Section( "Deny by Default Principle", - s""" + """ One important principle in security is the "deny by default" principle. You should use whitelisting, allow access only to what is needed. This is because it is easy to forget to deny something, and it is hard to remember everything that should be denied. @@ -75,7 +75,7 @@ object Authentication extends PhilosophyPage { There are also: - `excludeBranch("/somepath")` to exclude all paths starting with "/somepath" - - `excludeRegex("^/somepath/.*$$")` to exclude all paths matching the regex (be careful with this one!) + - `excludeRegex("^/somepath/.*\$")` to exclude all paths matching the regex (be careful with this one!) """.md ) } diff --git a/docs/src/files/philosophy/Authorization.scala b/docs/src/files/philosophy/Authorization.scala index b2d21dc..b64a1c3 100644 --- a/docs/src/files/philosophy/Authorization.scala +++ b/docs/src/files/philosophy/Authorization.scala @@ -1,6 +1,6 @@ package files.philosophy -import utils.Bundle.* +import utils.* object Authorization extends PhilosophyPage { diff --git a/docs/src/files/philosophy/DependencyInjection.scala b/docs/src/files/philosophy/DependencyInjection.scala index 4207601..b958ede 100644 --- a/docs/src/files/philosophy/DependencyInjection.scala +++ b/docs/src/files/philosophy/DependencyInjection.scala @@ -1,6 +1,6 @@ package files.philosophy -import utils.Bundle.* +import utils.* object DependencyInjection extends PhilosophyPage { diff --git a/docs/src/files/philosophy/Index.scala b/docs/src/files/philosophy/Index.scala index 7964348..4aadfc9 100644 --- a/docs/src/files/philosophy/Index.scala +++ b/docs/src/files/philosophy/Index.scala @@ -1,7 +1,6 @@ package files.philosophy -import utils.Bundle.* -import utils.Consts +import utils.* object Index extends PhilosophyPage { @@ -25,7 +24,7 @@ object Index extends PhilosophyPage { - [tupson](https://github.com/sake92/tupson) for JSON - [formson](${Consts.GhSourcesUrl}/formson) for forms - [validson](${Consts.GhSourcesUrl}/validson) for validation - - [hepek-components](https://github.com/sake92/hepek) for HTML (with [scalatags](https://github.com/com-lihaoyi/scalatags)) + - [scalatags](https://github.com/com-lihaoyi/scalatags) for HTML - [sttp](https://sttp.softwaremill.com/en/latest/) for firing HTTP requests - [typesafe-config](https://github.com/lightbend/config) for configuration diff --git a/docs/src/files/philosophy/PhilosophyPage.scala b/docs/src/files/philosophy/PhilosophyPage.scala index 6c717f0..2a20a6d 100644 --- a/docs/src/files/philosophy/PhilosophyPage.scala +++ b/docs/src/files/philosophy/PhilosophyPage.scala @@ -1,9 +1,6 @@ package files.philosophy -import utils.* -import Bundle.* - -trait PhilosophyPage extends DocPage { +trait PhilosophyPage extends utils.DocPage { override def categoryPosts = List( Index, @@ -16,5 +13,5 @@ trait PhilosophyPage extends DocPage { override def pageCategory = Some("Philosophy") - override def navbar = Some(Navbar.withActiveUrl(Index.ref)) + override def currentCategoryPage = Some(Index) } diff --git a/docs/src/files/philosophy/QueryParamsHandling.scala b/docs/src/files/philosophy/QueryParamsHandling.scala index 712d663..cd2a630 100644 --- a/docs/src/files/philosophy/QueryParamsHandling.scala +++ b/docs/src/files/philosophy/QueryParamsHandling.scala @@ -1,6 +1,6 @@ package files.philosophy -import utils.Bundle.* +import utils.* import files.howtos.QueryParams object QueryParamsHandling extends PhilosophyPage { diff --git a/docs/src/files/philosophy/RoutesMatching.scala b/docs/src/files/philosophy/RoutesMatching.scala index cf9a5ca..06d5987 100644 --- a/docs/src/files/philosophy/RoutesMatching.scala +++ b/docs/src/files/philosophy/RoutesMatching.scala @@ -1,6 +1,6 @@ package files.philosophy -import utils.Bundle.* +import utils.* object RoutesMatching extends PhilosophyPage { diff --git a/docs/src/files/reference/Index.scala b/docs/src/files/reference/Index.scala index 136e65f..f2e43c2 100644 --- a/docs/src/files/reference/Index.scala +++ b/docs/src/files/reference/Index.scala @@ -1,7 +1,7 @@ package files.reference +import scalatags.Text.all.* import utils.* -import Bundle.*, Tags.* object Index extends ReferencePage { @@ -15,11 +15,8 @@ object Index extends ReferencePage { s"${Consts.ProjectName} reference", div( s""" - ... - - ```scala - println("Hello!") - ``` + + Take a look at [Sharaf scaladoc](https://javadoc.io/doc/ba.sake/sharaf_3). """.md ) ) diff --git a/docs/src/files/reference/ReferencePage.scala b/docs/src/files/reference/ReferencePage.scala index 59db421..227039f 100644 --- a/docs/src/files/reference/ReferencePage.scala +++ b/docs/src/files/reference/ReferencePage.scala @@ -1,7 +1,7 @@ package files.reference +import files.tutorials.Index import utils.* -import Bundle.* trait ReferencePage extends DocPage { @@ -9,5 +9,5 @@ trait ReferencePage extends DocPage { override def pageCategory = Some("Reference") - override def navbar = Some(Navbar.withActiveUrl(Index.ref)) + override def currentCategoryPage = Some(Index) } diff --git a/docs/src/files/tutorials/HTML.scala b/docs/src/files/tutorials/HTML.scala index c5942b4..8326a51 100644 --- a/docs/src/files/tutorials/HTML.scala +++ b/docs/src/files/tutorials/HTML.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object HTML extends TutorialPage { diff --git a/docs/src/files/tutorials/HTMX.scala b/docs/src/files/tutorials/HTMX.scala index abc5f6a..4afb768 100644 --- a/docs/src/files/tutorials/HTMX.scala +++ b/docs/src/files/tutorials/HTMX.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object HTMX extends TutorialPage { @@ -21,7 +20,7 @@ object HTMX extends TutorialPage { Sharaf is using the [hepek-components](https://sake92.github.io/hepek/hepek/components/reference/bundle-reference.html) as its template engine, which has support for HTMX attributes. - You can lots of examples in [examples/scala-cli/htmx](${Consts.GhSourcesUrl}/examples/scala-cli/htmx) folder. + You can lots of examples in [examples/htmx](${Consts.GhSourcesUrl}/examples/htmx) folder. --- diff --git a/docs/src/files/tutorials/HandlingForms.scala b/docs/src/files/tutorials/HandlingForms.scala index c48609b..ad247d4 100644 --- a/docs/src/files/tutorials/HandlingForms.scala +++ b/docs/src/files/tutorials/HandlingForms.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object HandlingForms extends TutorialPage { diff --git a/docs/src/files/tutorials/HelloWorld.scala b/docs/src/files/tutorials/HelloWorld.scala index acce19e..4ad1857 100644 --- a/docs/src/files/tutorials/HelloWorld.scala +++ b/docs/src/files/tutorials/HelloWorld.scala @@ -1,7 +1,7 @@ package files.tutorials +import scalatags.Text.all.* import utils.* -import Bundle.*, Tags.* object HelloWorld extends TutorialPage { diff --git a/docs/src/files/tutorials/Index.scala b/docs/src/files/tutorials/Index.scala index 59a41c1..e369b2b 100644 --- a/docs/src/files/tutorials/Index.scala +++ b/docs/src/files/tutorials/Index.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object Index extends TutorialPage { @@ -55,7 +54,7 @@ object Index extends TutorialPage { "Examples", s""" - [scala-cli examples](${Consts.GhSourcesUrl}/examples/scala-cli), standalone examples using scala-cli - - [scala-cli HTMX examples](${Consts.GhSourcesUrl}/examples/scala-cli/htmx), standalone examples featuring HTMX + - [scala-cli HTMX examples](${Consts.GhSourcesUrl}/examples/htmx), standalone examples featuring HTMX - [API example](${Consts.GhSourcesUrl}/examples/api) featuring JSON and validation - [full-stack example](${Consts.GhSourcesUrl}/examples/fullstack) featuring HTML, static files and forms - [sharaf-todo-backend](https://github.com/sake92/sharaf-todo-backend), implementation of the [todobackend.com](http://todobackend.com/) spec, featuring CORS handling diff --git a/docs/src/files/tutorials/JsonAPI.scala b/docs/src/files/tutorials/JsonAPI.scala index 3427bee..1949efc 100644 --- a/docs/src/files/tutorials/JsonAPI.scala +++ b/docs/src/files/tutorials/JsonAPI.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object JsonAPI extends TutorialPage { @@ -12,11 +11,13 @@ object JsonAPI extends TutorialPage { super.blogSettings.withSections(modelSection, routesSection, runSection) private val snip1 = ScalaCliFiles.json_api.snippet(until = "val routes").indent(4) + private val snip2 = ScalaCliFiles.json_api - .snippet(from = "val routes", until = "Undertow.builder") + .snippet(from = "val routes", until = "UndertowSharafServer") .indent(4) .trim - private val snip3 = ScalaCliFiles.json_api.snippet(from = "Undertow.builder").indent(4) + + private val snip3 = ScalaCliFiles.json_api.snippet(from = "UndertowSharafServer").indent(4) val modelSection = Section( "Model definition", diff --git a/docs/src/files/tutorials/PathParams.scala b/docs/src/files/tutorials/PathParams.scala index 9df64f6..2879142 100644 --- a/docs/src/files/tutorials/PathParams.scala +++ b/docs/src/files/tutorials/PathParams.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object PathParams extends TutorialPage { diff --git a/docs/src/files/tutorials/QueryParams.scala b/docs/src/files/tutorials/QueryParams.scala index 0daabb3..ef52f22 100644 --- a/docs/src/files/tutorials/QueryParams.scala +++ b/docs/src/files/tutorials/QueryParams.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object QueryParams extends TutorialPage { diff --git a/docs/src/files/tutorials/SqlDb.scala b/docs/src/files/tutorials/SqlDb.scala index 6acde39..f837603 100644 --- a/docs/src/files/tutorials/SqlDb.scala +++ b/docs/src/files/tutorials/SqlDb.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object SqlDb extends TutorialPage { @@ -35,10 +34,10 @@ object SqlDb extends TutorialPage { private val snip1 = ScalaCliFiles.sql_db.snippet(until = "case class Customer").indent(4) private val snip2 = ScalaCliFiles.sql_db - .snippet(from = "case class Customer", until = "Undertow.builder") + .snippet(from = "case class Customer", until = "UndertowSharafServer") .indent(4) .trim - private val snip3 = ScalaCliFiles.sql_db.snippet(from = "Undertow.builder").indent(4) + private val snip3 = ScalaCliFiles.sql_db.snippet(from = "UndertowSharafServer").indent(4) val squerySetup = Section( "Squery setup", diff --git a/docs/src/files/tutorials/StaticFiles.scala b/docs/src/files/tutorials/StaticFiles.scala index 0c161a0..2eaf9fd 100644 --- a/docs/src/files/tutorials/StaticFiles.scala +++ b/docs/src/files/tutorials/StaticFiles.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object StaticFiles extends TutorialPage { diff --git a/docs/src/files/tutorials/Tests.scala b/docs/src/files/tutorials/Tests.scala index deba8c1..8ca4f09 100644 --- a/docs/src/files/tutorials/Tests.scala +++ b/docs/src/files/tutorials/Tests.scala @@ -1,7 +1,7 @@ package files.tutorials +import scalatags.Text.all.* import utils.* -import Bundle.*, Tags.* object Tests extends TutorialPage { diff --git a/docs/src/files/tutorials/TutorialPage.scala b/docs/src/files/tutorials/TutorialPage.scala index 9e0c5af..6016879 100644 --- a/docs/src/files/tutorials/TutorialPage.scala +++ b/docs/src/files/tutorials/TutorialPage.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* // TODO logging, logback + slf4j // TODO docker @@ -34,5 +33,5 @@ trait TutorialPage extends DocPage { override def pageCategory = Some("Tutorials") - override def navbar = Some(Navbar.withActiveUrl(Index.ref)) + override def currentCategoryPage = Some(Index) } diff --git a/docs/src/files/tutorials/Validation.scala b/docs/src/files/tutorials/Validation.scala index d5ac0ca..cee8979 100644 --- a/docs/src/files/tutorials/Validation.scala +++ b/docs/src/files/tutorials/Validation.scala @@ -1,7 +1,6 @@ package files.tutorials import utils.* -import Bundle.* object Validation extends TutorialPage { @@ -57,7 +56,7 @@ object Validation extends TutorialPage { "invalidArguments": [ { "reason": "must not be blank", - "path": "$$.brand", + "path": "\\$$.brand", "value": "" } ], @@ -88,12 +87,12 @@ object Validation extends TutorialPage { "invalidArguments": [ { "reason": "must not be blank", - "path": "$$.brand", + "path": "\\$$.brand", "value": " " }, { "reason": "must not be negative", - "path": "$$.quantity", + "path": "\\$$.quantity", "value": "-5" } ], diff --git a/docs/src/utils/Consts.scala b/docs/src/utils/Consts.scala index b31628e..564af9b 100644 --- a/docs/src/utils/Consts.scala +++ b/docs/src/utils/Consts.scala @@ -6,7 +6,7 @@ object Consts: val ArtifactOrg = "ba.sake" val ArtifactName = "sharaf" - val ArtifactVersion = "0.9.2" + val ArtifactVersion = "0.10.0" val GhHandle = "sake92" val GhProjectName = "sharaf" @@ -14,3 +14,9 @@ object Consts: val GhSourcesUrl = s"https://github.com/${GhHandle}/${GhProjectName}/tree/main" val tq = """"""""" + + def allSearchIndexedPages = Seq(files.Index) ++ + files.tutorials.Index.categoryPosts ++ + files.howtos.Index.categoryPosts ++ + files.reference.Index.categoryPosts ++ + files.philosophy.Index.categoryPosts diff --git a/docs/src/utils/ScalaCliFiles.scala b/docs/src/utils/ScalaCliFiles.scala index c6c0cc1..14eb24f 100644 --- a/docs/src/utils/ScalaCliFiles.scala +++ b/docs/src/utils/ScalaCliFiles.scala @@ -18,22 +18,22 @@ extension (str: String) { object ScalaCliFiles: - val hello = get("hello.sc") - val path_params = get("path_params.sc") - val query_params = get("query_params.sc") - val static_files = get("static_files.sc") - val html_scalatags = get("html_scalatags.sc") - val html_hepek = get("html_hepek.sc") - val htmx_load_snippet = get(os.RelPath("htmx") / "htmx_load_snippet.sc") - val form_handling = get("form_handling.sc") - val json_api = get("json_api.sc") - val json_api_test = get("json_api.test.scala") + val hello = get(os.RelPath("scala-cli/hello.sc")) + val path_params = get(os.RelPath("scala-cli/path_params.sc")) + val query_params = get(os.RelPath("scala-cli/query_params.sc")) + val static_files = get(os.RelPath("scala-cli/static_files.sc")) + val html_scalatags = get(os.RelPath("scala-cli/html_scalatags.sc")) + val html_hepek = get(os.RelPath("scala-cli/html_hepek.sc")) + val htmx_load_snippet = get(os.RelPath("htmx/htmx_load_snippet.sc")) + val form_handling = get(os.RelPath("scala-cli/form_handling.sc")) + val json_api = get(os.RelPath("scala-cli/json_api.sc")) + val json_api_test = get(os.RelPath("scala-cli/json_api.test.scala")) - val sql_db = get("sql_db.sc") + val sql_db = get(os.RelPath("scala-cli/sql_db.sc")) - val validation = get("validation.sc") + val validation = get(os.RelPath("scala-cli/validation.sc")) private def get(chunk: os.PathChunk) = // os.pwd is sandboxed, this is called from plugin ! val wd = os.Path(System.getenv("MILL_WORKSPACE_ROOT")) - os.read(wd / "examples" / "scala-cli" / chunk) + os.read(wd / "examples" / chunk).replace("${", "\\${") // escaping for nodejs shiki diff --git a/docs/src/utils/package.scala b/docs/src/utils/package.scala index 3e26f72..dc185c2 100644 --- a/docs/src/utils/package.scala +++ b/docs/src/utils/package.scala @@ -1,59 +1,35 @@ package utils +import scalatags.Text.all.* import ba.sake.hepek.core.RelativePath -import ba.sake.hepek.html.statik.BlogPostPage -import ba.sake.hepek.bootstrap5.statik.BootstrapStaticBundle +import ba.sake.hepek.html.statik -val Bundle = locally { - val b = BootstrapStaticBundle.default - import b.* +type Section = statik.Section +val Section = statik.Section - val ratios = Ratios.default.withSingle(1, 2, 1).withHalf(1, 1).withThird(1, 2, 1) - val grid = Grid.withScreenRatios( - Grid.screenRatios.withSm(None).withXs(None).withLg(ratios).withMd(ratios) - ) - b.withGrid(grid) -} - -val FA = ba.sake.hepek.fontawesome5.FA +def pager(thisSp: statik.BlogPostPage)(using caller: RelativePath) = { -def pager(thisSp: BlogPostPage)(using caller: RelativePath) = { - import Bundle.Tags.* - - def bsNavigation(navLinks: Frag*) = tag("nav")( - ul(cls := "pagination justify-content-center")(navLinks) + def picoButtons(navLinks: Frag*) = tag("nav")( + div(role := "group")(navLinks) ) val posts = thisSp.categoryPosts val indexOfThis = posts.indexOf(thisSp) if posts.length > 1 && indexOfThis >= 0 then { - if indexOfThis == 0 then - bsNavigation( - li(cls := "disabled page-item")( - a(href := "#", cls := "page-link")("Previous") - ), - li(title := posts(indexOfThis + 1).pageSettings.label, cls := "page-item")( - a(href := posts(indexOfThis + 1).ref, cls := "page-link")("Next") - ) + picoButtons( + a(href := "#", disabled, role := "button", cls := "outline")("Previous"), + a(href := posts(indexOfThis + 1).ref, role := "button", cls := "outline")("Next") ) else if indexOfThis == posts.length - 1 then - bsNavigation( - li(title := posts(indexOfThis - 1).pageSettings.label, cls := "page-item")( - a(href := posts(indexOfThis - 1).ref, cls := "page-link")("Previous") - ), - li(cls := "disabled page-item")( - a(href := "#", cls := "page-link")("Next") - ) + picoButtons( + a(href := posts(indexOfThis - 1).ref, role := "button", cls := "outline")("Previous"), + a(href := "#", disabled, role := "button", cls := "outline")("Next") ) else - bsNavigation( - li(title := posts(indexOfThis - 1).pageSettings.label, cls := "page-item")( - a(href := posts(indexOfThis - 1).ref, cls := "page-link")("Previous") - ), - li(title := posts(indexOfThis + 1).pageSettings.label, cls := "page-item")( - a(href := posts(indexOfThis + 1).ref, cls := "page-link")("Next") - ) + picoButtons( + a(href := posts(indexOfThis - 1).ref, role := "button", cls := "outline")("Previous"), + a(href := posts(indexOfThis + 1).ref, role := "button", cls := "outline")("Next") ) } else frag() diff --git a/docs/src/utils/templates.scala b/docs/src/utils/templates.scala index 31fd4cc..4586dbf 100644 --- a/docs/src/utils/templates.scala +++ b/docs/src/utils/templates.scala @@ -1,12 +1,51 @@ package utils -import ba.sake.hepek.prismjs.PrismDependencies -import ba.sake.hepek.theme.bootstrap5.* +import scalatags.Text.all.* +import scalatags.Text.tags2.{aside, main, nav, section} import ba.sake.hepek.anchorjs.AnchorjsDependencies -import ba.sake.hepek.fontawesome5.FADependencies -import Bundle.*, Tags.* +import ba.sake.hepek.html.statik.{BlogPostPage, ShikiSettings, StaticPage} +import files.SearchResults + +trait DocPage extends DocStaticPage with BlogPostPage { + + override def mainContent: Frag = div(cls := "blog-post")( + aside(id := "left-menu")( + nav( + ul( + for sameCatPost <- categoryPosts + yield li( + a( + href := sameCatPost.ref, + Option.when(this.relPath == sameCatPost.relPath)(attr("aria-current") := "page") + )( + sameCatPost.pageSettings.label + ) + ) + ) + ) + ), + div(cls := "main-content")( + pager(this), + renderSections(blogSettings.sections, 2) + ) + ) + + private def renderSections(secs: List[Section], depth: Int): List[Frag] = + secs.map { s => + // h2, h3... + val hTag = tag("h" + depth) + section(id := s.id)( + hTag(s.name), + s.content, + renderSections(s.children, depth + 1) + ) + } +} + +trait DocStaticPage extends StaticPage with AnchorjsDependencies { + + def currentCategoryPage: Option[StaticPage] = None -trait DocStaticPage extends StaticPage with AnchorjsDependencies with FADependencies { override def staticSiteSettings = super.staticSiteSettings .withIndexPage(files.Index) .withMainPages( @@ -18,29 +57,75 @@ trait DocStaticPage extends StaticPage with AnchorjsDependencies with FADependen override def siteSettings = super.siteSettings .withName(Consts.ProjectName) - .withFaviconNormal(files.images.`favicon.svg`.ref) - .withFaviconInverted(files.images.`favicon.svg`.ref) - - override def bodyContent = frag( - super.bodyContent, - footer(Classes.txtAlignCenter, Classes.bgInfo, cls := "fixed-bottom")( - a(href := Consts.GhUrl, Classes.btnClass)(FA.github()), - a(href := "https://discord.gg/g9KVY3WkMG", Classes.btnClass)(FA.discord()) + .withFaviconNormal(files.images.`favicon.png`.ref) + + override def shikiSettings = super.shikiSettings.withTheme("material-theme-ocean") + + override def styleURLs = + List("https://cdn.jsdelivr.net/npm/@picocss/pico@2.1.1/css/pico.cyan.min.css", files.styles.`main.css`.ref) + + override def pageContent = frag( + header(cls := "container")( + topNavbar + ), + main(cls := "container")( + mainContent + ), + footer(cls := "container flex-centered")( + a(href := "https://github.com/sake92/sharaf")( + raw(""" + + + + + """) + ), + a(href := "https://discord.gg/g9KVY3WkMG")( + raw(""" + + + + + + """) + ) ) ) - override def styleURLs = super.styleURLs - .appended(files.styles.`main.css`.ref) + private def topNavbar = nav( + ul( + siteSettings.faviconNormal.map { fav => + li(img(src := fav)) + }, + li(a(href := "https://sake92.github.io/sharaf/")("Sharaf Docs")) + ), + ul( + li( + form(action := SearchResults.ref, method := "GET")( + input( + name := "q", + tpe := "search", + placeholder := "Search" + ) + ) + ) + ), + ul( + for + mainPage <- staticSiteSettings.mainPages + labela = mainPage.pageCategory.getOrElse(mainPage.pageSettings.label) + yield li( + a( + href := mainPage.ref, + Option.when(currentCategoryPage.map(_.relPath).contains(mainPage.relPath))( + attr("aria-current") := "true" + ) + )(labela) + ) + ) + ) override def scriptURLs = super.scriptURLs .appended(files.scripts.`main.js`.ref) } - -trait DocPage extends DocStaticPage with HepekBootstrap5BlogPage with PrismDependencies { - - override def tocSettings = Some(TocSettings(tocType = TocType.Scrollspy(offset = 60))) - - override def pageHeader = Some(pager(this)) - -} diff --git a/examples/scala-cli/htmx/README.md b/examples/htmx/README.md similarity index 64% rename from examples/scala-cli/htmx/README.md rename to examples/htmx/README.md index ab7b906..e590e0a 100644 --- a/examples/scala-cli/htmx/README.md +++ b/examples/htmx/README.md @@ -3,12 +3,7 @@ Example implementations of https://htmx.org/examples/ Run any of these from this folder: ```sh -scala-cli htmx_load_snippet.sc -``` - -For examples that use images and static resources: -```sh -scala-cli htmx_click_to_load.sc --resource-dir resources +scala-cli htmx_load_snippet.sc --resource-dir resources ``` If you want to restart the server when files change, just add the `--restart` flag: diff --git a/examples/scala-cli/htmx/htmx_active_search.sc b/examples/htmx/htmx_active_search.sc similarity index 96% rename from examples/scala-cli/htmx/htmx_active_search.sc rename to examples/htmx/htmx_active_search.sc index e4270df..5b6dd75 100644 --- a/examples/scala-cli/htmx/htmx_active_search.sc +++ b/examples/htmx/htmx_active_search.sc @@ -1,12 +1,11 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/active-search/ -// scala htmx_active_search.sc --resource-dir resources -import io.undertow.Undertow import ba.sake.formson.FormDataRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -17,11 +16,7 @@ val routes = Routes: val contactsSlice = allContacts.filter(_.matches(formData.search)) Response.withBody(views.contactsRows(contactsSlice)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_animations.sc b/examples/htmx/htmx_animations.sc similarity index 91% rename from examples/scala-cli/htmx/htmx_animations.sc rename to examples/htmx/htmx_animations.sc index 1f0471b..b52e0f6 100644 --- a/examples/scala-cli/htmx/htmx_animations.sc +++ b/examples/htmx/htmx_animations.sc @@ -1,13 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/animations/ -// scala htmx_animations.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -38,11 +37,7 @@ val routes = Routes: Thread.sleep(1000) // simulate sloww Response.withBody("Submitted!") -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_bulk_update.sc b/examples/htmx/htmx_bulk_update.sc similarity index 91% rename from examples/scala-cli/htmx/htmx_bulk_update.sc rename to examples/htmx/htmx_bulk_update.sc index 3d79f7b..c20199c 100644 --- a/examples/scala-cli/htmx/htmx_bulk_update.sc +++ b/examples/htmx/htmx_bulk_update.sc @@ -1,12 +1,11 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/bulk-update/ -// scala htmx_bulk_update.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer import ba.sake.formson.FormDataRW import ba.sake.hepek.htmx.* @@ -26,11 +25,7 @@ val routes = Routes: } Response.withBody(views.contactsRows(currentContacts, AffectedContacts(formData.ids, false))) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_cascading_selects.sc b/examples/htmx/htmx_cascading_selects.sc similarity index 88% rename from examples/scala-cli/htmx/htmx_cascading_selects.sc rename to examples/htmx/htmx_cascading_selects.sc index 014bf9b..ba5c2e8 100644 --- a/examples/scala-cli/htmx/htmx_cascading_selects.sc +++ b/examples/htmx/htmx_cascading_selects.sc @@ -1,13 +1,13 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/value-select/ -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* import ba.sake.querson.QueryStringRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -17,11 +17,7 @@ val routes = Routes: val qp = Request.current.queryParams[ModelsQP] Response.withBody(views.cascadingSelect(qp.make)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_click_edit.sc b/examples/htmx/htmx_click_edit.sc similarity index 89% rename from examples/scala-cli/htmx/htmx_click_edit.sc rename to examples/htmx/htmx_click_edit.sc index 8396713..5519251 100644 --- a/examples/scala-cli/htmx/htmx_click_edit.sc +++ b/examples/htmx/htmx_click_edit.sc @@ -1,11 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/click-to-edit/ -import io.undertow.Undertow + import scalatags.Text.all.{param =>_, *} import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer import ba.sake.formson.FormDataRW var currentValue = ContactForm("Joe", "Blow", "joe@blow.com") @@ -22,11 +23,7 @@ val routes = Routes: currentValue = formData Response.withBody(views.contactView(currentValue)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_click_to_load.sc b/examples/htmx/htmx_click_to_load.sc similarity index 87% rename from examples/scala-cli/htmx/htmx_click_to_load.sc rename to examples/htmx/htmx_click_to_load.sc index d55747a..db99778 100644 --- a/examples/scala-cli/htmx/htmx_click_to_load.sc +++ b/examples/htmx/htmx_click_to_load.sc @@ -1,15 +1,14 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/click-to-load/ -// scala htmx_click_to_load.sc --resource-dir resources import java.util.UUID -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* import ba.sake.querson.QueryStringRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val PageSize = 5 @@ -26,11 +25,7 @@ val routes = Routes: val contactsSlice = allContacts.slice(qp.page * PageSize, qp.page * PageSize + PageSize) Response.withBody(views.contactsRowsWithButton(contactsSlice, qp.page)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_delete_row.sc b/examples/htmx/htmx_delete_row.sc similarity index 83% rename from examples/scala-cli/htmx/htmx_delete_row.sc rename to examples/htmx/htmx_delete_row.sc index 29cd2d1..96307ba 100644 --- a/examples/scala-cli/htmx/htmx_delete_row.sc +++ b/examples/htmx/htmx_delete_row.sc @@ -1,13 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/delete-row/ -// scala htmx_delete_row.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer var allContacts = Seq( Contact("1", "Angie MacDowell", "angie@macdowell.org"), @@ -22,11 +21,7 @@ val routes = Routes: allContacts = allContacts.filterNot(_.id == id) Response.withBody("") // empty string, remove that -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_dialogs_bootstrap.sc b/examples/htmx/htmx_dialogs_bootstrap.sc similarity index 85% rename from examples/scala-cli/htmx/htmx_dialogs_bootstrap.sc rename to examples/htmx/htmx_dialogs_bootstrap.sc index 1562153..b77e260 100644 --- a/examples/scala-cli/htmx/htmx_dialogs_bootstrap.sc +++ b/examples/htmx/htmx_dialogs_bootstrap.sc @@ -1,13 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/modal-bootstrap/ -// scala htmx_dialogs_bootstrap.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -15,11 +14,7 @@ val routes = Routes: case GET -> Path("modal") => Response.withBody(views.bsDialog()) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_dialogs_bootstrap_form.sc b/examples/htmx/htmx_dialogs_bootstrap_form.sc similarity index 87% rename from examples/scala-cli/htmx/htmx_dialogs_bootstrap_form.sc rename to examples/htmx/htmx_dialogs_bootstrap_form.sc index 91f211b..c90d60a 100644 --- a/examples/scala-cli/htmx/htmx_dialogs_bootstrap_form.sc +++ b/examples/htmx/htmx_dialogs_bootstrap_form.sc @@ -1,14 +1,13 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // example of BS5 modal with a form -// scala htmx_dialogs_bootstrap_form.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* import ba.sake.formson.FormDataRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -20,11 +19,7 @@ val routes = Routes: val formData = Request.current.bodyForm[DialogForm] Response.withBody(div(s"You submitted: $formData")) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_dialogs_browser.sc b/examples/htmx/htmx_dialogs_browser.sc similarity index 75% rename from examples/scala-cli/htmx/htmx_dialogs_browser.sc rename to examples/htmx/htmx_dialogs_browser.sc index 02aeceb..003f66f 100644 --- a/examples/scala-cli/htmx/htmx_dialogs_browser.sc +++ b/examples/htmx/htmx_dialogs_browser.sc @@ -1,13 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/dialogs/ -// scala htmx_dialogs_browser.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer import ba.sake.sharaf.htmx.* val routes = Routes: @@ -22,11 +21,7 @@ val routes = Routes: ) ) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_edit_row.sc b/examples/htmx/htmx_edit_row.sc similarity index 91% rename from examples/scala-cli/htmx/htmx_edit_row.sc rename to examples/htmx/htmx_edit_row.sc index 1309bca..80373ed 100644 --- a/examples/scala-cli/htmx/htmx_edit_row.sc +++ b/examples/htmx/htmx_edit_row.sc @@ -1,14 +1,13 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/edit-row/ -// scala htmx_edit_row.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* import ba.sake.formson.FormDataRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer var allContacts = Seq( Contact("1", "Joe Smith", "joe@smith.org"), @@ -38,11 +37,7 @@ val routes = Routes: allContacts = allContacts.updated(idx, updatedContact) Response.withBody(views.viewContactRow(updatedContact)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_file_upload_js.sc b/examples/htmx/htmx_file_upload_js.sc similarity index 84% rename from examples/scala-cli/htmx/htmx_file_upload_js.sc rename to examples/htmx/htmx_file_upload_js.sc index fe30b0d..302e91a 100644 --- a/examples/scala-cli/htmx/htmx_file_upload_js.sc +++ b/examples/htmx/htmx_file_upload_js.sc @@ -1,11 +1,11 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* import ba.sake.formson.FormDataRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer // https://htmx.org/examples/file-upload/ // scala htmx_file_upload_js.sc --resource-dir resources @@ -18,11 +18,7 @@ val routes = Routes: val fileUpload = Request.current.bodyForm[FileUpload] Response.withBody(div(s"Upload done!")) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_infinite_scroll.sc b/examples/htmx/htmx_infinite_scroll.sc similarity index 87% rename from examples/scala-cli/htmx/htmx_infinite_scroll.sc rename to examples/htmx/htmx_infinite_scroll.sc index 6791beb..1414282 100644 --- a/examples/scala-cli/htmx/htmx_infinite_scroll.sc +++ b/examples/htmx/htmx_infinite_scroll.sc @@ -1,15 +1,14 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/click-to-load/ -// scala htmx_infinite_scroll.sc --resource-dir resources import java.util.UUID -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* import ba.sake.querson.QueryStringRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val PageSize = 10 @@ -26,11 +25,7 @@ val routes = Routes: val contactsSlice = allContacts.slice(qp.page * PageSize, qp.page * PageSize + PageSize) Response.withBody(views.contactsRows(contactsSlice, qp.page)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_inline_validation.sc b/examples/htmx/htmx_inline_validation.sc similarity index 88% rename from examples/scala-cli/htmx/htmx_inline_validation.sc rename to examples/htmx/htmx_inline_validation.sc index ce67d68..02ac2df 100644 --- a/examples/scala-cli/htmx/htmx_inline_validation.sc +++ b/examples/htmx/htmx_inline_validation.sc @@ -1,13 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/inline-validation/ -// scala htmx_inline_validation.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer import ba.sake.formson.FormDataRW val routes = Routes: @@ -22,11 +21,7 @@ val routes = Routes: val formData = Request.current.bodyForm[ContactForm] Response.withBody(views.contactForm(formData)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_lazy_load.sc b/examples/htmx/htmx_lazy_load.sc similarity index 76% rename from examples/scala-cli/htmx/htmx_lazy_load.sc rename to examples/htmx/htmx_lazy_load.sc index a21614b..7f99100 100644 --- a/examples/scala-cli/htmx/htmx_lazy_load.sc +++ b/examples/htmx/htmx_lazy_load.sc @@ -1,13 +1,12 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 // https://htmx.org/examples/lazy-load/ -// scala htmx_lazy_load.sc --resource-dir resources -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -17,11 +16,7 @@ val routes = Routes: val graph = img(src := "/img/tokyo.png") Response.withBody(graph) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_load_snippet.sc b/examples/htmx/htmx_load_snippet.sc similarity index 65% rename from examples/scala-cli/htmx/htmx_load_snippet.sc rename to examples/htmx/htmx_load_snippet.sc index d220baf..9e89de8 100644 --- a/examples/scala-cli/htmx/htmx_load_snippet.sc +++ b/examples/htmx/htmx_load_snippet.sc @@ -1,12 +1,10 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -// scala htmx_load_snippet.sc --resource-dir resources - -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -19,11 +17,7 @@ val routes = Routes: ) ) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_progress_bar.sc b/examples/htmx/htmx_progress_bar.sc similarity index 91% rename from examples/scala-cli/htmx/htmx_progress_bar.sc rename to examples/htmx/htmx_progress_bar.sc index 5badb51..f15c681 100644 --- a/examples/scala-cli/htmx/htmx_progress_bar.sc +++ b/examples/htmx/htmx_progress_bar.sc @@ -1,15 +1,14 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 import java.util.concurrent.TimeUnit // https://htmx.org/examples/progress-bar/ -// scala htmx_progress_bar.sc --resource-dir resources import java.util.concurrent.Executors -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer import ba.sake.sharaf.htmx.ResponseHeaders var percentage = 0 @@ -40,11 +39,7 @@ val routes = Routes: then Response.withBody(bar).settingHeader(ResponseHeaders.Trigger, "done") else Response.withBody(bar) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/htmx_tabs_hateoas.sc b/examples/htmx/htmx_tabs_hateoas.sc similarity index 79% rename from examples/scala-cli/htmx/htmx_tabs_hateoas.sc rename to examples/htmx/htmx_tabs_hateoas.sc index 56af18a..cdc27c8 100644 --- a/examples/scala-cli/htmx/htmx_tabs_hateoas.sc +++ b/examples/htmx/htmx_tabs_hateoas.sc @@ -1,12 +1,10 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -// scala htmx_tabs_hateoas.sc --resource-dir resources - -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.htmx.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -18,11 +16,7 @@ val routes = Routes: case GET -> Path("tab3") => Response.withBody(tabSnippet(3)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/htmx/resources/public/img/bars.svg b/examples/htmx/resources/public/img/bars.svg similarity index 100% rename from examples/scala-cli/htmx/resources/public/img/bars.svg rename to examples/htmx/resources/public/img/bars.svg diff --git a/examples/scala-cli/htmx/resources/public/img/tokyo.png b/examples/htmx/resources/public/img/tokyo.png similarity index 100% rename from examples/scala-cli/htmx/resources/public/img/tokyo.png rename to examples/htmx/resources/public/img/tokyo.png diff --git a/examples/scala-cli/README.md b/examples/scala-cli/README.md new file mode 100644 index 0000000..65674f8 --- /dev/null +++ b/examples/scala-cli/README.md @@ -0,0 +1,17 @@ + +Example implementations of https://htmx.org/examples/ + +Run any of these from this folder: +```sh +scala-cli hello.sc --resource-dir resources +``` + +If you want to restart the server when files change, just add the `--restart` flag: +```sh +scala-cli hello.sc --resource-dir resources --restart +``` + + + + + diff --git a/examples/scala-cli/demo.sc b/examples/scala-cli/demo.sc index cb0080e..5c4fc7b 100644 --- a/examples/scala-cli/demo.sc +++ b/examples/scala-cli/demo.sc @@ -1,11 +1,11 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import ba.sake.querson.QueryStringRW import ba.sake.tupson.JsonRW import ba.sake.validson.Validator -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path("cars") => @@ -20,12 +20,8 @@ val routes = Routes: CarsDb.add(newCar) Response.withBody(newCar) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler( - SharafHandler(routes).withExceptionMapper(ExceptionMapper.json) - ) - .build +UndertowSharafServer("localhost", 8181, routes) + .withExceptionMapper(ExceptionMapper.json) .start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/form_handling.sc b/examples/scala-cli/form_handling.sc index 8bec4ef..36f24e7 100644 --- a/examples/scala-cli/form_handling.sc +++ b/examples/scala-cli/form_handling.sc @@ -1,11 +1,10 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.formson.FormDataRW -import ba.sake.sharaf.*, routing.* - +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -14,11 +13,7 @@ val routes = Routes: val formData = Request.current.bodyForm[ContactUsForm] Response.withBody(s"Got form data: ${formData}") -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println("Server started at http://localhost:8181") diff --git a/examples/scala-cli/hello.sc b/examples/scala-cli/hello.sc index cda7cff..c9d81eb 100644 --- a/examples/scala-cli/hello.sc +++ b/examples/scala-cli/hello.sc @@ -1,17 +1,13 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path("hello", name) => Response.withBody(s"Hello $name") -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/html_hepek.sc b/examples/scala-cli/html_hepek.sc index 0ed2e54..5063c1c 100644 --- a/examples/scala-cli/html_hepek.sc +++ b/examples/scala-cli/html_hepek.sc @@ -1,10 +1,10 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import scalatags.Text.all.* import ba.sake.hepek.html.HtmlPage -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.{*, given} val routes = Routes: case GET -> Path() => @@ -12,11 +12,7 @@ val routes = Routes: case GET -> Path("hello", name) => Response.withBody(HelloView(name)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/html_scalatags.sc b/examples/scala-cli/html_scalatags.sc index aea52e2..fa2e3ed 100644 --- a/examples/scala-cli/html_scalatags.sc +++ b/examples/scala-cli/html_scalatags.sc @@ -1,9 +1,9 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import scalatags.Text.all.* -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => @@ -11,11 +11,7 @@ val routes = Routes: case GET -> Path("hello", name) => Response.withBody(HelloView(name)) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/json_api.sc b/examples/scala-cli/json_api.sc index 48e31ee..8bb2976 100644 --- a/examples/scala-cli/json_api.sc +++ b/examples/scala-cli/json_api.sc @@ -1,9 +1,18 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import ba.sake.tupson.JsonRW -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer + +case class Car(brand: String, model: String, quantity: Int) derives JsonRW + +object CarsDb { + var db: Seq[Car] = Seq() + def findAll(): Seq[Car] = db + def findByBrand(brand: String): Seq[Car] = db.filter(_.brand == brand) + def add(car: Car): Unit = db = db.appended(car) +} val routes = Routes: case GET -> Path("cars") => @@ -18,21 +27,8 @@ val routes = Routes: CarsDb.add(reqBody) Response.withBody(reqBody) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler( - SharafHandler(routes).withExceptionMapper(ExceptionMapper.json) - ) - .build +UndertowSharafServer("localhost", 8181, routes) + .withExceptionMapper(ExceptionMapper.json) .start() println("Server started at http://localhost:8181") - -case class Car(brand: String, model: String, quantity: Int) derives JsonRW - -object CarsDb { - var db: Seq[Car] = Seq() - def findAll(): Seq[Car] = db - def findByBrand(brand: String): Seq[Car] = db.filter(_.brand == brand) - def add(car: Car): Unit = db = db.appended(car) -} \ No newline at end of file diff --git a/examples/scala-cli/json_api.test.scala b/examples/scala-cli/json_api.test.scala index 60f5eab..541e66e 100644 --- a/examples/scala-cli/json_api.test.scala +++ b/examples/scala-cli/json_api.test.scala @@ -1,6 +1,7 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 -//> using test.dep org.scalameta::munit::1.1.0 +//> using scala "3.7.0" +//> using dep ba.sake::tupson:0.13.0 +//> using dep com.lihaoyi::requests:0.9.0 +//> using test.dep org.scalameta::munit::1.1.1 import ba.sake.tupson.* @@ -15,7 +16,7 @@ class JsonApiSuite extends munit.FunSuite { val res = requests.get(s"$baseUrl/cars") val resBody = res.text.parseJson[Seq[Car]] assertEquals(res.statusCode, 200) - assertEquals(res.headers("content-type"), Seq("application/json")) + assertEquals(res.headers("content-type"), Seq("application/json; charset=utf-8")) assertEquals(res.text.parseJson[Seq[Car]], Seq.empty) } @@ -29,7 +30,7 @@ class JsonApiSuite extends munit.FunSuite { val res = requests.get(s"$baseUrl/cars/Mercedes") val resBody = res.text.parseJson[Seq[Car]] assertEquals(res.statusCode, 200) - assertEquals(res.headers("content-type"), Seq("application/json")) + assertEquals(res.headers("content-type"), Seq("application/json; charset=utf-8")) assertEquals(resBody, Seq(Car("Mercedes", "ML350", 1))) } } diff --git a/examples/scala-cli/path_params.sc b/examples/scala-cli/path_params.sc index 773861a..de1bb41 100644 --- a/examples/scala-cli/path_params.sc +++ b/examples/scala-cli/path_params.sc @@ -1,8 +1,8 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path("string", x) => @@ -11,10 +11,6 @@ val routes = Routes: case GET -> Path("int", param[Int](x)) => Response.withBody(s"int = ${x}") -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/query_params.sc b/examples/scala-cli/query_params.sc index a10663a..45f1868 100644 --- a/examples/scala-cli/query_params.sc +++ b/examples/scala-cli/query_params.sc @@ -1,10 +1,9 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import ba.sake.querson.QueryStringRW -import ba.sake.sharaf.*, routing.* - +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path("raw") => @@ -16,10 +15,6 @@ val routes = Routes: val qp = Request.current.queryParams[SearchParams] Response.withBody(s"params = ${qp}") -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/sql_db.sc b/examples/scala-cli/sql_db.sc index 94f058f..63d9a6e 100644 --- a/examples/scala-cli/sql_db.sc +++ b/examples/scala-cli/sql_db.sc @@ -1,13 +1,13 @@ -//> using scala "3.6.4" +//> using scala "3.7.0" //> using dep org.postgresql:postgresql:42.7.5 //> using dep com.zaxxer:HikariCP:6.3.0 -//> using dep ba.sake::sharaf:0.9.2 +//> using dep ba.sake::sharaf-undertow:0.10.0 //> using dep ba.sake::squery:0.7.0 -import io.undertow.Undertow import ba.sake.tupson.JsonRW import ba.sake.squery.{*, given} -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val ds = com.zaxxer.hikari.HikariDataSource() ds.setJdbcUrl("jdbc:postgresql://localhost:5432/postgres") @@ -35,10 +35,6 @@ val routes = Routes: } Response.withBody(customer) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/static_files.sc b/examples/scala-cli/static_files.sc index 575d6ce..c7d3acf 100644 --- a/examples/scala-cli/static_files.sc +++ b/examples/scala-cli/static_files.sc @@ -1,17 +1,13 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path() => Response.withBody("Try /example.js") -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler(SharafHandler(routes)) - .build - .start() +UndertowSharafServer("localhost", 8181, routes).start() println(s"Server started at http://localhost:8181") diff --git a/examples/scala-cli/validation.sc b/examples/scala-cli/validation.sc index a574ab0..c60d393 100644 --- a/examples/scala-cli/validation.sc +++ b/examples/scala-cli/validation.sc @@ -1,11 +1,11 @@ -//> using scala "3.6.4" -//> using dep ba.sake::sharaf:0.9.2 +//> using scala "3.7.0" +//> using dep ba.sake::sharaf-undertow:0.10.0 -import io.undertow.Undertow import ba.sake.querson.QueryStringRW import ba.sake.tupson.JsonRW import ba.sake.validson.Validator -import ba.sake.sharaf.*, routing.* +import ba.sake.sharaf.* +import ba.sake.sharaf.undertow.UndertowSharafServer val routes = Routes: case GET -> Path("cars") => @@ -16,12 +16,8 @@ val routes = Routes: val json = Request.current.bodyJsonValidated[Car] Response.withBody(CarApiResult(s"JSON body OK: ${json}")) -Undertow.builder - .addHttpListener(8181, "localhost") - .setHandler( - SharafHandler(routes).withExceptionMapper(ExceptionMapper.json) - ) - .build +UndertowSharafServer("localhost", 8181, routes) + .withExceptionMapper(ExceptionMapper.json) .start() println(s"Server started at http://localhost:8181") diff --git a/examples/snunit/README.md b/examples/snunit/README.md new file mode 100644 index 0000000..3f5d3b3 --- /dev/null +++ b/examples/snunit/README.md @@ -0,0 +1,46 @@ + +You need `clang` for Scala Native: `sudo apt install clang`. + +The following steps are done from root of this git repo. +Build the app: + +```shell +./mill -i examples.snunit.nativeLink +``` + + +## Run and configure unitd + +```shell +sudo unitd --no-daemon --log /dev/stdout --control unix:control.sock +``` + +Make `config.json` in with this content: + +```json +{ + "listeners": { + "*:8081": { + "pass": "applications/myapp" + } + }, + "applications": { + "myapp": { + "type": "external", + "executable": "out/examples/snunit/nativeLink.dest/out" + } + } +} +``` + +Then in another shell: + +```shell +curl -X PUT --unix-socket control.sock -d @config.json localhost/config + +curl localhost:8081 +# should return "Hello Snunit!" +``` + + + diff --git a/examples/snunit/src/Main.scala b/examples/snunit/src/Main.scala new file mode 100644 index 0000000..7087f01 --- /dev/null +++ b/examples/snunit/src/Main.scala @@ -0,0 +1,13 @@ +import ba.sake.sharaf.* +import ba.sake.sharaf.snunit.* + +@main def main: Unit = + + val routes = Routes { case _ => + Response.withBody("Hello Snunit!") + } + val server = _root_.snunit.SyncServerBuilder + .setRequestHandler(SharafRequestHandler(routes)) + .build() + + server.listen() diff --git a/mill b/mill index d087ac0..555d7fc 100755 --- a/mill +++ b/mill @@ -1,20 +1,39 @@ #!/usr/bin/env sh -# This is a wrapper script, that automatically download mill from GitHub release pages -# You can give the required mill version with --mill-version parameter -# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. # -# Original Project page: https://github.com/lefou/millw -# Script Version: 0.4.12 +# This script determines the Mill version to use by trying these sources +# - env-variable `MILL_VERSION` +# - local file `.mill-version` +# - local file `.config/mill-version` +# - `mill-version` from YAML fronmatter of current buildfile +# - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) +# - env-variable `DEFAULT_MILL_VERSION` +# +# If a version has the suffix '-native' a native binary will be used. +# If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. +# If no such suffix is found, the script will pick a default based on version and platform. +# +# Once a version was determined, it tries to use either +# - a system-installed mill, if found and it's version matches +# - an already downloaded version under ~/.cache/mill/download +# +# If no working mill version was found on the system, +# this script downloads a binary file from Maven Central or Github Pages (this is version dependent) +# into a cache location (~/.cache/mill/download). +# +# Mill Project URL: https://github.com/com-lihaoyi/mill +# Script Version: 1.0.0-M1-49-ac90e3 # # If you want to improve this script, please also contribute your changes back! +# This script was generated from: dist/scripts/src/mill.sh # # Licensed under the Apache License, Version 2.0 set -e if [ -z "${DEFAULT_MILL_VERSION}" ] ; then - DEFAULT_MILL_VERSION="0.11.4" + DEFAULT_MILL_VERSION=0.12.14 fi @@ -31,16 +50,17 @@ fi # Explicit commandline argument takes precedence over all other methods if [ "$1" = "--mill-version" ] ; then - shift - if [ "x$1" != "x" ] ; then - MILL_VERSION="$1" - shift - else - echo "You specified --mill-version without a version." 1>&2 - echo "Please provide a version that matches one provided on" 1>&2 - echo "${MILL_REPO_URL}/releases" 1>&2 - false - fi + echo "The --mill-version option is no longer supported." 1>&2 +fi + +MILL_BUILD_SCRIPT="" + +if [ -f "build.mill" ] ; then + MILL_BUILD_SCRIPT="build.mill" +elif [ -f "build.mill.scala" ] ; then + MILL_BUILD_SCRIPT="build.mill.scala" +elif [ -f "build.sc" ] ; then + MILL_BUILD_SCRIPT="build.sc" fi # Please note, that if a MILL_VERSION is already set in the environment, @@ -52,6 +72,8 @@ if [ -z "${MILL_VERSION}" ] ; then MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" elif [ -f ".config/mill-version" ] ; then MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then + MILL_VERSION="$(cat ${MILL_BUILD_SCRIPT} | grep '//[|] *mill-version: *' | sed 's;//| *mill-version: *;;')" fi fi @@ -65,7 +87,7 @@ fi if [ -z "${MILL_VERSION}" ] ; then # TODO: try to load latest version from release page echo "No mill version specified." 1>&2 - echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + echo "You should provide a version via a '//| mill-version: ' comment or a '.mill-version' file." 1>&2 mkdir -p "${MILL_DOWNLOAD_PATH}" LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( @@ -101,7 +123,60 @@ if [ -z "${MILL_VERSION}" ] ; then fi fi -MILL="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" +MILL_NATIVE_SUFFIX="-native" +MILL_JVM_SUFFIX="-jvm" +FULL_MILL_VERSION=$MILL_VERSION +ARTIFACT_SUFFIX="" +set_artifact_suffix(){ + if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then + if [ "$(uname -m)" = "aarch64" ]; then + ARTIFACT_SUFFIX="-native-linux-aarch64" + else + ARTIFACT_SUFFIX="-native-linux-amd64" + fi + elif [ "$(uname)" = "Darwin" ]; then + if [ "$(uname -m)" = "arm64" ]; then + ARTIFACT_SUFFIX="-native-mac-aarch64" + else + ARTIFACT_SUFFIX="-native-mac-amd64" + fi + else + echo "This native mill launcher supports only Linux and macOS." 1>&2 + exit 1 + fi +} + +case "$MILL_VERSION" in + *"$MILL_NATIVE_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} + set_artifact_suffix + ;; + + *"$MILL_JVM_SUFFIX") + MILL_VERSION=${MILL_VERSION%"$MILL_JVM_SUFFIX"} + ;; + + *) + case "$MILL_VERSION" in + 0.1.*) ;; + 0.2.*) ;; + 0.3.*) ;; + 0.4.*) ;; + 0.5.*) ;; + 0.6.*) ;; + 0.7.*) ;; + 0.8.*) ;; + 0.9.*) ;; + 0.10.*) ;; + 0.11.*) ;; + 0.12.*) ;; + *) + set_artifact_suffix + esac + ;; +esac + +MILL="${MILL_DOWNLOAD_PATH}/$MILL_VERSION$ARTIFACT_SUFFIX" try_to_use_system_mill() { if [ "$(uname)" != "Linux" ]; then @@ -174,49 +249,59 @@ EOF try_to_use_system_mill # If not already downloaded, download it -if [ ! -s "${MILL}" ] ; then - - # support old non-XDG download dir - MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" - OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" - if [ -x "${OLD_MILL}" ] ; then - MILL="${OLD_MILL}" +if [ ! -s "${MILL}" ] || [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + case $MILL_VERSION in + 0.12.0 | 0.12.1 | 0.12.2 | 0.12.3 | 0.12.4 | 0.12.5 | 0.12.6 | 0.12.7 | 0.12.8 | 0.12.9 | 0.12.10 | 0.12.11 ) + DOWNLOAD_EXT="jar" + ;; + 0.12.* ) + DOWNLOAD_EXT="exe" + ;; + 0.* ) + DOWNLOAD_EXT="jar" + ;; + *) + DOWNLOAD_EXT="exe" + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${DOWNLOAD_EXT}" else - case $MILL_VERSION in - 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) - DOWNLOAD_SUFFIX="" - DOWNLOAD_FROM_MAVEN=0 - ;; - 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) - DOWNLOAD_SUFFIX="-assembly" - DOWNLOAD_FROM_MAVEN=0 - ;; - *) - DOWNLOAD_SUFFIX="-assembly" - DOWNLOAD_FROM_MAVEN=1 - ;; - esac - - DOWNLOAD_FILE=$(mktemp mill.XXXXXX) - - if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then - DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/${MILL_VERSION}/mill-dist-${MILL_VERSION}.jar" - else - MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') - DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" - unset MILL_VERSION_TAG - fi - - # TODO: handle command not found - echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 - ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" - chmod +x "${DOWNLOAD_FILE}" - mkdir -p "${MILL_DOWNLOAD_PATH}" - mv "${DOWNLOAD_FILE}" "${MILL}" + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi - unset DOWNLOAD_FILE - unset DOWNLOAD_SUFFIX + if [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then + echo $DOWNLOAD_URL + echo $MILL + exit 0 fi + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX fi if [ -z "$MILL_MAIN_CLI" ] ; then @@ -236,6 +321,7 @@ unset OLD_MILL unset MILL_VERSION unset MILL_REPO_URL +# -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 # We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes # shellcheck disable=SC2086 -exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" diff --git a/mill.bat b/mill.bat index 6957369..85f9605 100644 --- a/mill.bat +++ b/mill.bat @@ -1,13 +1,32 @@ @echo off -rem This is a wrapper script, that automatically download mill from GitHub release pages -rem You can give the required mill version with --mill-version parameter -rem If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +rem This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. rem -rem Original Project page: https://github.com/lefou/millw -rem Script Version: 0.4.12 +rem This script determines the Mill version to use by trying these sources +rem - env-variable `MILL_VERSION` +rem - local file `.mill-version` +rem - local file `.config/mill-version` +rem - `mill-version` from YAML fronmatter of current buildfile +rem - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) +rem - env-variable `DEFAULT_MILL_VERSION` +rem +rem If a version has the suffix '-native' a native binary will be used. +rem If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. +rem If no such suffix is found, the script will pick a default based on version and platform. +rem +rem Once a version was determined, it tries to use either +rem - a system-installed mill, if found and it's version matches +rem - an already downloaded version under %USERPROFILE%\.mill\download +rem +rem If no working mill version was found on the system, +rem this script downloads a binary file from Maven Central or Github Pages (this is version dependent) +rem into a cache location (%USERPROFILE%\.mill\download). +rem +rem Mill Project URL: https://github.com/com-lihaoyi/mill +rem Script Version: 1.0.0-M1-49-ac90e3 rem rem If you want to improve this script, please also contribute your changes back! +rem This script was generated from: dist/scripts/src/mill.bat rem rem Licensed under the Apache License, Version 2.0 @@ -15,153 +34,224 @@ rem setlocal seems to be unavailable on Windows 95/98/ME rem but I don't think we need to support them in 2019 setlocal enabledelayedexpansion -if [!DEFAULT_MILL_VERSION!]==[] ( - set "DEFAULT_MILL_VERSION=0.11.4" -) +if [!DEFAULT_MILL_VERSION!]==[] ( set "DEFAULT_MILL_VERSION=0.12.10" ) -if [!GITHUB_RELEASE_CDN!]==[] ( - set "GITHUB_RELEASE_CDN=" -) +if [!MILL_GITHUB_RELEASE_CDN!]==[] ( set "MILL_GITHUB_RELEASE_CDN=" ) -if [!MILL_MAIN_CLI!]==[] ( - set "MILL_MAIN_CLI=%~f0" -) +if [!MILL_MAIN_CLI!]==[] ( set "MILL_MAIN_CLI=%~f0" ) set "MILL_REPO_URL=https://github.com/com-lihaoyi/mill" -rem %~1% removes surrounding quotes -if [%~1%]==[--mill-version] ( - if not [%~2%]==[] ( - set MILL_VERSION=%~2% - rem shift command doesn't work within parentheses - set "STRIP_VERSION_PARAMS=true" - ) else ( - echo You specified --mill-version without a version. 1>&2 - echo Please provide a version that matches one provided on 1>&2 - echo %MILL_REPO_URL%/releases 1>&2 - exit /b 1 - ) -) +SET MILL_BUILD_SCRIPT= -if not defined STRIP_VERSION_PARAMS GOTO AfterStripVersionParams -rem strip the: --mill-version {version} -shift -shift -:AfterStripVersionParams +if exist "build.mill" ( + set MILL_BUILD_SCRIPT=build.mill +) else ( + if exist "build.mill.scala" ( + set MILL_BUILD_SCRIPT=build.mill.scala + ) else ( + if exist "build.sc" ( + set MILL_BUILD_SCRIPT=build.sc + ) else ( + rem no-op + ) + ) +) if [!MILL_VERSION!]==[] ( if exist .mill-version ( - set /p MILL_VERSION=<.mill-version + set /p MILL_VERSION=<.mill-version ) else ( if exist .config\mill-version ( set /p MILL_VERSION=<.config\mill-version + ) else ( + if not "%MILL_BUILD_SCRIPT%"=="" ( + for /f "tokens=1-2*" %%a in ('findstr /C:"//| mill-version:" %MILL_BUILD_SCRIPT%') do ( + set "MILL_VERSION=%%c" + ) + ) else ( + rem no-op + ) ) ) ) -if [!MILL_VERSION!]==[] ( - set MILL_VERSION=%DEFAULT_MILL_VERSION% -) +if [!MILL_VERSION!]==[] set MILL_VERSION=%DEFAULT_MILL_VERSION% -if [!MILL_DOWNLOAD_PATH!]==[] ( - set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download -) +if [!MILL_DOWNLOAD_PATH!]==[] set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download rem without bat file extension, cmd doesn't seem to be able to run it -set MILL=%MILL_DOWNLOAD_PATH%\!MILL_VERSION!.bat -if not exist "%MILL%" ( - set VERSION_PREFIX=%MILL_VERSION:~0,4% - rem Since 0.5.0 - set DOWNLOAD_SUFFIX=-assembly - rem Since 0.11.0 - set DOWNLOAD_FROM_MAVEN=1 - if [!VERSION_PREFIX!]==[0.0.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.1.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.2.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.3.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.4.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 +set "MILL_NATIVE_SUFFIX=-native" +set "MILL_JVM_SUFFIX=-jvm" +set "FULL_MILL_VERSION=%MILL_VERSION%" +set "MILL_EXT=.bat" +set "ARTIFACT_SUFFIX=" +REM Check if MILL_VERSION contains MILL_NATIVE_SUFFIX +echo !MILL_VERSION! | findstr /C:"%MILL_NATIVE_SUFFIX%" >nul +if !errorlevel! equ 0 ( + set "MILL_VERSION=%MILL_VERSION:-native=%" + REM -native images compiled with graal do not support windows-arm + REM https://github.com/oracle/graal/issues/9215 + IF /I NOT "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( + set "ARTIFACT_SUFFIX=-native-windows-amd64" + set "MILL_EXT=.exe" + ) else ( + rem no-op ) - if [!VERSION_PREFIX!]==[0.5.] ( - set DOWNLOAD_FROM_MAVEN=0 +) else ( + echo !MILL_VERSION! | findstr /C:"%MILL_JVM_SUFFIX%" >nul + if !errorlevel! equ 0 ( + set "MILL_VERSION=%MILL_VERSION:-jvm=%" + ) else ( + set "SKIP_VERSION=false" + set "MILL_PREFIX=%MILL_VERSION:~0,4%" + if "!MILL_PREFIX!"=="0.1." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.2." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.3." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.4." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.5." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.6." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.7." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.8." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.9." set "SKIP_VERSION=true" + set "MILL_PREFIX=%MILL_VERSION:~0,5%" + if "!MILL_PREFIX!"=="0.10." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.11." set "SKIP_VERSION=true" + if "!MILL_PREFIX!"=="0.12." set "SKIP_VERSION=true" + + if "!SKIP_VERSION!"=="false" ( + IF /I NOT "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( + set "ARTIFACT_SUFFIX=-native-windows-amd64" + set "MILL_EXT=.exe" + ) + ) else ( + rem no-op + ) ) - if [!VERSION_PREFIX!]==[0.6.] ( - set DOWNLOAD_FROM_MAVEN=0 +) + +set MILL=%MILL_DOWNLOAD_PATH%\!FULL_MILL_VERSION!!MILL_EXT! + +set MILL_RESOLVE_DOWNLOAD= + +if not exist "%MILL%" ( + set MILL_RESOLVE_DOWNLOAD=true +) else ( + if defined MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT ( + set MILL_RESOLVE_DOWNLOAD=true + ) else ( + rem no-op ) - if [!VERSION_PREFIX!]==[0.7.] ( - set DOWNLOAD_FROM_MAVEN=0 +) + + +if [!MILL_RESOLVE_DOWNLOAD!]==[true] ( + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,4% + set MILL_SHORT_VERSION_PREFIX=%MILL_VERSION:~0,2% + rem Since 0.5.0 + set MILL_DOWNLOAD_SUFFIX=-assembly + rem Since 0.11.0 + set MILL_DOWNLOAD_FROM_MAVEN=1 + if [!MILL_VERSION_PREFIX!]==[0.0.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 ) - if [!VERSION_PREFIX!]==[0.8.] ( - set DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.1.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 ) - if [!VERSION_PREFIX!]==[0.9.] ( - set DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.2.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 ) - set VERSION_PREFIX=%MILL_VERSION:~0,5% - if [!VERSION_PREFIX!]==[0.10.] ( - set DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.3.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 ) - set VERSION_PREFIX=%MILL_VERSION:~0,8% - if [!VERSION_PREFIX!]==[0.11.0-M] ( - set DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.4.] ( + set MILL_DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FROM_MAVEN=0 ) - set VERSION_PREFIX= + if [!MILL_VERSION_PREFIX!]==[0.5.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.6.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.7.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.8.] set MILL_DOWNLOAD_FROM_MAVEN=0 + if [!MILL_VERSION_PREFIX!]==[0.9.] set MILL_DOWNLOAD_FROM_MAVEN=0 + + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,5% + if [!MILL_VERSION_PREFIX!]==[0.10.] set MILL_DOWNLOAD_FROM_MAVEN=0 + + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,8% + if [!MILL_VERSION_PREFIX!]==[0.11.0-M] set MILL_DOWNLOAD_FROM_MAVEN=0 + + set MILL_VERSION_PREFIX=%MILL_VERSION:~0,5% + set DOWNLOAD_EXT=exe + if [!MILL_SHORT_VERSION_PREFIX!]==[0.] set DOWNLOAD_EXT=jar + if [!MILL_VERSION_PREFIX!]==[0.12.] set DOWNLOAD_EXT=exe + if [!MILL_VERSION!]==[0.12.0] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.1] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.2] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.3] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.4] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.5] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.6] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.7] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.8] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.9] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.10] set DOWNLOAD_EXT=jar + if [!MILL_VERSION!]==[0.12.11] set DOWNLOAD_EXT=jar + + set MILL_VERSION_PREFIX= + set MILL_SHORT_VERSION_PREFIX= for /F "delims=- tokens=1" %%A in ("!MILL_VERSION!") do set MILL_VERSION_BASE=%%A + set MILL_VERSION_MILESTONE= for /F "delims=- tokens=2" %%A in ("!MILL_VERSION!") do set MILL_VERSION_MILESTONE=%%A - set VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! - if [!VERSION_MILESTONE_START!]==[M] ( - set MILL_VERSION_TAG="!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE!" + set MILL_VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! + if [!MILL_VERSION_MILESTONE_START!]==[M] ( + set MILL_VERSION_TAG=!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE! ) else ( set MILL_VERSION_TAG=!MILL_VERSION_BASE! ) - - rem there seems to be no way to generate a unique temporary file path (on native Windows) - set DOWNLOAD_FILE=%MILL%.tmp - - if [!DOWNLOAD_FROM_MAVEN!]==[1] ( - set DOWNLOAD_URL=https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/!MILL_VERSION!/mill-dist-!MILL_VERSION!.jar + if [!MILL_DOWNLOAD_FROM_MAVEN!]==[1] ( + set MILL_DOWNLOAD_URL=https://repo1.maven.org/maven2/com/lihaoyi/mill-dist!ARTIFACT_SUFFIX!/!MILL_VERSION!/mill-dist!ARTIFACT_SUFFIX!-!MILL_VERSION!.!DOWNLOAD_EXT! ) else ( - set DOWNLOAD_URL=!GITHUB_RELEASE_CDN!%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!DOWNLOAD_SUFFIX! + set MILL_DOWNLOAD_URL=!MILL_GITHUB_RELEASE_CDN!%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!MILL_DOWNLOAD_SUFFIX! + ) + + if defined MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT ( + echo !MILL_DOWNLOAD_URL! + echo !MILL! + exit /b 0 ) - echo Downloading mill %MILL_VERSION% from !DOWNLOAD_URL! ... 1>&2 + rem there seems to be no way to generate a unique temporary file path (on native Windows) + set MILL_DOWNLOAD_FILE=%MILL%.tmp + + echo Downloading mill !MILL_VERSION! from !MILL_DOWNLOAD_URL! ... 1>&2 if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" rem curl is bundled with recent Windows 10 rem but I don't think we can expect all the users to have it in 2019 where /Q curl - if %ERRORLEVEL% EQU 0 ( - curl -f -L "!DOWNLOAD_URL!" -o "!DOWNLOAD_FILE!" + if !ERRORLEVEL! EQU 0 ( + curl -f -L "!MILL_DOWNLOAD_URL!" -o "!MILL_DOWNLOAD_FILE!" ) else ( rem bitsadmin seems to be available on Windows 7 rem without /dynamic, github returns 403 rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground - bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!DOWNLOAD_URL!" "!DOWNLOAD_FILE!" + bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!MILL_DOWNLOAD_URL!" "!MILL_DOWNLOAD_FILE!" ) - if not exist "!DOWNLOAD_FILE!" ( - echo Could not download mill %MILL_VERSION% 1>&2 + if not exist "!MILL_DOWNLOAD_FILE!" ( + echo Could not download mill !MILL_VERSION! 1>&2 exit /b 1 ) - move /y "!DOWNLOAD_FILE!" "%MILL%" + move /y "!MILL_DOWNLOAD_FILE!" "%MILL%" - set DOWNLOAD_FILE= - set DOWNLOAD_SUFFIX= + set MILL_DOWNLOAD_FILE= + set MILL_DOWNLOAD_SUFFIX= ) set MILL_DOWNLOAD_PATH= @@ -197,24 +287,10 @@ if [%~1%]==[--bsp] ( set "MILL_PARAMS=%*%" if not [!MILL_FIRST_ARG!]==[] ( - if defined STRIP_VERSION_PARAMS ( - for /f "tokens=1-3*" %%a in ("%*") do ( - set "MILL_PARAMS=%%d" - ) - ) else ( - for /f "tokens=1*" %%a in ("%*") do ( - set "MILL_PARAMS=%%b" - ) - ) -) else ( - if defined STRIP_VERSION_PARAMS ( - for /f "tokens=1-2*" %%a in ("%*") do ( - rem strip %%a - It's the "--mill-version" option. - rem strip %%b - it's the version number that comes after the option. - rem keep %%c - It's the remaining options. - set "MILL_PARAMS=%%c" - ) + for /f "tokens=1*" %%a in ("%*") do ( + set "MILL_PARAMS=%%b" ) ) -"%MILL%" %MILL_FIRST_ARG% -D "mill.main.cli=%MILL_MAIN_CLI%" %MILL_PARAMS% \ No newline at end of file +rem -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 +"%MILL%" %MILL_FIRST_ARG% -D "mill.main.cli=%MILL_MAIN_CLI%" %MILL_PARAMS% diff --git a/sharaf-snunit/src/ba/sake/sharaf/snunit/ResponseUtils.scala b/sharaf-snunit/src/ba/sake/sharaf/snunit/ResponseUtils.scala new file mode 100644 index 0000000..3c40e78 --- /dev/null +++ b/sharaf-snunit/src/ba/sake/sharaf/snunit/ResponseUtils.scala @@ -0,0 +1,3 @@ +package ba.sake.sharaf.snunit + + diff --git a/sharaf-snunit/src/ba/sake/sharaf/snunit/SharafRequestHandler.scala b/sharaf-snunit/src/ba/sake/sharaf/snunit/SharafRequestHandler.scala new file mode 100644 index 0000000..412e538 --- /dev/null +++ b/sharaf-snunit/src/ba/sake/sharaf/snunit/SharafRequestHandler.scala @@ -0,0 +1,51 @@ +package ba.sake.sharaf.snunit + +import snunit.{Request as SnunitRequest, *} +import ba.sake.sharaf.* +import ba.sake.sharaf.routing.* +import java.io.ByteArrayOutputStream + +class SharafRequestHandler(routes: Routes) extends RequestHandler { + override def handleRequest(snunitRequest: SnunitRequest): Unit = { + given Request = SnunitSharafRequest.create(snunitRequest) + val reqParams = fillReqParams(snunitRequest) + routes.definition.lift(reqParams) match { + case Some(res) => + val headers = buildHeaders(res.headerUpdates) + res.body match { + case Some(body) => + val aos = new ByteArrayOutputStream + res.rw.write(body, aos) + send(snunitRequest)(StatusCode(res.status.code), aos.toByteArray(), headers) + case None => + send(snunitRequest)(StatusCode(res.status.code), "", headers) + } + case None => + // will be catched by ExceptionHandler + throw exceptions.NotFoundException("route") + } + } + + private def buildHeaders(hu: HeaderUpdates): Headers = { + val headerValues = hu.updates.flatMap { + case HeaderUpdate.Set(name, values) => + Seq(name.value -> values.head) + case _ => Seq.empty + } + Headers(headerValues*) + } + + private def fillReqParams(req: SnunitRequest): RequestParams = { + val method = HttpMethod.valueOf(req.method) + val originalPath = req.path + val relPath = + if originalPath.startsWith("/") then originalPath.drop(1) + else originalPath + val pathSegments = relPath.split("/") + val path = + if pathSegments.size == 1 && pathSegments.head == "" + then Path() + else Path(pathSegments*) + (method, path) + } +} diff --git a/sharaf-snunit/src/ba/sake/sharaf/snunit/SnunitSharafRequest.scala b/sharaf-snunit/src/ba/sake/sharaf/snunit/SnunitSharafRequest.scala new file mode 100644 index 0000000..e2ae99b --- /dev/null +++ b/sharaf-snunit/src/ba/sake/sharaf/snunit/SnunitSharafRequest.scala @@ -0,0 +1,44 @@ +package ba.sake.sharaf.snunit + +import java.nio.charset.StandardCharsets +import scala.jdk.CollectionConverters.* +import scala.jdk.StreamConverters.* +import snunit.{Request as SnunitRequest, *} +import ba.sake.formson.* +import ba.sake.querson.* +import ba.sake.sharaf.* +import ba.sake.sharaf.exceptions.* + +class SnunitSharafRequest(underlyingRequest: SnunitRequest) extends Request { + + /* *** HEADERS *** */ + def headers: Map[HttpString, Seq[String]] = + val underlyingHeaders = underlyingRequest.headers + underlyingHeaders.toMap + .map { (headerName, headerValue) => + HttpString(headerName) -> Seq(headerValue) + } + + def cookies: Seq[Cookie] = ??? // TODO + // underlyingHttpServerExchange.requestCookies().asScala.map(CookieUtils.fromUndertow).toSeq + + /* *** QUERY *** */ + override lazy val queryParamsRaw: QueryStringMap = + underlyingRequest.query.split("&").flatMap( _.split("=") match { + case Array(key, value) => Seq(key -> Seq(value)) + case _ => Seq.empty + }) + .toMap + + /* *** BODY *** */ + override lazy val bodyString: String = + String(underlyingRequest.contentRaw(), StandardCharsets.UTF_8) + + def bodyFormRaw: FormDataMap = ??? // TODO +} + +object SnunitSharafRequest { + + def create(underlyingRequest: SnunitRequest): SnunitSharafRequest = + SnunitSharafRequest(underlyingRequest) +} diff --git a/sharaf-snunit/test/src/.gitkeep b/sharaf-snunit/test/src/.gitkeep new file mode 100644 index 0000000..e69de29