Skip to content

Commit

Permalink
Merge pull request #5 from Quafadas/iteratorTidy
Browse files Browse the repository at this point in the history
Introduce typed CSVs
  • Loading branch information
Quafadas authored Jan 3, 2025
2 parents a9f17ba + 7adf941 commit 663d2d2
Show file tree
Hide file tree
Showing 22 changed files with 2,296 additions and 417 deletions.
2 changes: 1 addition & 1 deletion .mill-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.12.3
0.12.5
19 changes: 12 additions & 7 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
version = "3.6.1"
version = "3.8.4"
project.git = true
runner.dialect = scala3

rewrite.scala3.convertToNewSyntax = true
rewrite.rules = [RedundantBraces]
rewrite.scala3.removeOptionalBraces = yes
runner.dialectOverride.withAllowToplevelTerms = true
runner.dialectOverride.withAllowEndMarker = true
runner.dialectOverride.allowSignificantIndentation = true
rewrite.scala3.countEndMarkerLines = lastBlockOnly
rewrite.scala3.insertEndMarkerMinLines = 1
indent.main = 2
indent.defnSite = 2

align.preset = more // For pretty alignment.
maxColumn = 150 // For my wide 30" display.
runner.dialect = scala3
maxColumn = 120
project.excludeFilters = [ ".*/build\\.mill"]
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@

[docs](http://localhost:3002/docs/index.html)

# SCala AUto TABLE
Auto-magically generate html tables from case classes

- Strongly typed compile-time CSV
- pretty printing to console for `Product` types
- Auto-magically generate html tables from case classes
- Searchable, sortable browser GUI for your tables

## Elevator Pitch
One line CSV import.

Insta-auto-magically generate a [scalatags](https://github.com/com-lihaoyi/scalatags) table from a `Seq[A]` (case classes).

with scala-cli
Expand All @@ -28,17 +36,28 @@ It cross compiles, and gives you back a scalatags table

## Infrequently Asked Questions
### Is this project a good idea
Unclear. But I wanted to play with Mirrors.

And have a "real" library to test build cross building stuff, GHA etc on.
Unclear. One of it's purposes is to push the boundary of my metaprogramming knowledge. If you use this, it exposes you to the very real risk of the reality that this is an educational project I run on my own time.

### How does it work

The macro stuff came from smashing my head against chatGPT and a wall. A lot.

The table derivation stuff comes from here;
I aggressively copy pasted everything from here and poked it with a sharp stick until it did what I wanted.
https://blog.philipp-martini.de/blog/magic-mirror-scala3/

### Limitations
See tests;

For the desktop show part -
- Formatting is implied by the type. To format your own types, you'll need to write a given for it.
- Extension is through the type system, have a look at the JVM tests for an example if writing a given for your own custom type
- As I don't _really_ understand how it works, it's unlikely to get extended further...
- Extending it further is probably a really bad idea anyway
- Extending it further is probably a really bad idea anyway

For the CSV part :
- It is assumed you have one header row, and that your headers are reasonably representaable by the compiler.
- As of early 2024 there is a compiler bug that reverses the order of large named tuples. CSV files over 22 might get weird - I don't believe the limitation to be fundaemntal, just need to wait for the fix.

// TODO: Docs

// TODO: Sample (graduated)
34 changes: 31 additions & 3 deletions build.sc
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import $ivy.`com.github.lolgab::mill-crossplatform::0.2.4`
import $ivy.`io.github.quafadas:millSite_mill0.12_2.13:0.0.38`
import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.4.0`
import $ivy.`com.lihaoyi::mill-contrib-buildinfo:`

import mill.contrib.buildinfo.BuildInfo
import de.tobiasroeser.mill.vcs.version._
import com.github.lolgab.mill.crossplatform._
import mill._, mill.scalalib._, mill.scalajslib._, mill.scalanativelib._
Expand All @@ -19,8 +21,7 @@ trait Common extends ScalaModule with PublishModule {
ivy"com.lihaoyi::os-lib:0.11.3",
ivy"com.lihaoyi::fansi::0.5.0"
)

def ammoniteVersion = "3.0.0"
override def scalacOptions: T[Seq[String]] = super.scalacOptions() ++ Seq("-experimental", "-language:experimental.namedTuples", "-Xmax-inlines", "128")

def publishVersion = VcsVersion.vcsState().format()

Expand Down Expand Up @@ -60,7 +61,32 @@ object scautable extends CrossPlatform {
}
object jvm extends Shared {
// jvm specific settings here
object test extends ScalaTests with SharedTests
object test extends ScalaTests with SharedTests with BuildInfo {

def buildInfoPackageName: String = "io.github.quafadas.scautable"


override def generatedSources: T[Seq[PathRef]] = T{
val resourceDir = resources().map(_.path).zipWithIndex.map{case (str, i) => s"""final val resourceDir$i = \"\"\"$str${java.io.File.separator}\"\"\"""" }.mkString("\n\t")
val fileName = "BuildInfo.scala"
val code = s"""

package io.github.quafadas.scautable

/**
Resources are not available at compile time. This is a workaround to get the path to the resource directory, (allowing unit testing of a macro based on a local file).
*/

object Generated {$resourceDir
}
"""
val dest = T.ctx().dest / "BuildInfo.scala"
os.write(dest , code)
Seq(PathRef(dest))

}

}
}
object js extends Shared with CommonJS {
// js specific settings here
Expand All @@ -73,6 +99,8 @@ object site extends SiteModule {

def scalaVersion = scautable.jvm.scalaVersion

override def scalacOptions: T[Seq[String]] = super.scalacOptions() ++ scautable.jvm.scalacOptions()

override def moduleDeps = Seq(scautable.jvm)

}
62 changes: 48 additions & 14 deletions scautable/jvm/src/jvmSpecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package io.github.quafadas.scautable

import java.awt.Desktop
import io.github.quafadas.scautable.scautable.HtmlTableRender
import NamedTuple.*
// import almond.api.JupyterApi
// import almond.interpreter.api.DisplayData
// import almond.api.JupyterAPIHolder.value

trait PlatformSpecific {
trait PlatformSpecific:

private def openBrowserWindow(uri: java.net.URI): Unit = {
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) then Desktop.getDesktop().browse(uri)
private def openBrowserWindow(uri: java.net.URI): Unit =
if Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE) then
Desktop.getDesktop().browse(uri)
else
/* Hail Mary...
https://stackoverflow.com/questions/5226212/how-to-open-the-default-webbrowser-using-java
Expand All @@ -29,16 +31,47 @@ trait PlatformSpecific {
*/
val runtime = java.lang.Runtime.getRuntime()
runtime.exec(Array[String](s"""xdg-open $uri]"""))
}

/**
* Attempts to open a browser window, and display this Seq of `Product` as a table.
inline def desktopShowNt[K <: Tuple, V <: Tuple](a: Seq[NamedTuple[K, V]])(using
tableDeriveInstance: HtmlTableRender[V]
): os.Path =
val asString = scautable.nt(a).toString()
val theHtml = raw"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
</head>
<body>
<div>
$asString
</div>
<script>
$$(document).ready( function () {
$$('#scautable').DataTable({
pageLength: 50
});
} );
</script>
</body>
</html>"""
val tempFi = os.temp(theHtml, suffix = ".html", prefix = "plot-", deleteOnExit = false)
openBrowserWindow(tempFi.toNIO.toUri())
tempFi
end desktopShowNt

/** Attempts to open a browser window, and display this Seq of `Product` as a table.
*
* @param a - seq of case classes
* @param tableDeriveInstance - summon a HtmlTableRender instance for the case class
* @param a
* \- seq of case classes
* @param tableDeriveInstance
* \- summon a HtmlTableRender instance for the case class
* @return
*/
def desktopShow[A <: Product](a: Seq[A])(using tableDeriveInstance: HtmlTableRender[A]) = {
inline def desktopShow[A <: Product](a: Seq[A])(using tableDeriveInstance: HtmlTableRender[A]) =
val asString = scautable(a).toString()
val theHtml = raw"""
<!DOCTYPE html>
Expand All @@ -55,19 +88,20 @@ trait PlatformSpecific {
</div>
<script>
$$(document).ready( function () {
$$('#scautable').DataTable();
$$('#scautable').DataTable({
pageLength: 50
});
} );
</script>
</body>
</html>"""
val tempFi = os.temp(theHtml, suffix = ".html", prefix = "plot-")
val tempFi = os.temp(theHtml, suffix = ".html", prefix = "plot-", deleteOnExit = false)
openBrowserWindow(tempFi.toNIO.toUri())
tempFi
}
end desktopShow

// def almondShow[A <: Product](a: Seq[A])(using tableDeriveInstance: HtmlTableRender[A]) =
// val kernel = summon[JupyterApi]
// val asString = scautable(a).toString()
// kernel.publish.html(asString)

}
end PlatformSpecific
Loading

0 comments on commit 663d2d2

Please sign in to comment.