Port to Scala 3
This commit is contained in:
parent
d9a9e6b445
commit
f955a7563c
13
build.sc
13
build.sc
|
@ -1,9 +1,8 @@
|
|||
import mill._, scalalib._, publish._
|
||||
import $ivy.`com.lihaoyi::mill-contrib-bloop:0.9.5`
|
||||
|
||||
|
||||
object copret extends ScalaModule with PublishModule {
|
||||
def scalaVersion = "2.13.3"
|
||||
def scalaVersion = "3.1.0"
|
||||
|
||||
def publishVersion = "0.0.1"
|
||||
def pomSettings = PomSettings(
|
||||
|
@ -14,13 +13,13 @@ object copret extends ScalaModule with PublishModule {
|
|||
licenses = Seq(License.MIT),
|
||||
developers = Seq(
|
||||
Developer("crater2150", "Alexander Gehrke", "https://github.com/crater2150")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def ivyDeps = Agg(
|
||||
ivy"org.jline:jline:3.19.0",
|
||||
ivy"com.lihaoyi::ammonite-ops:2.3.8",
|
||||
ivy"com.lihaoyi::fansi:0.2.10",
|
||||
)
|
||||
ivy"com.lihaoyi::ammonite-ops:2.3.8".withDottyCompat(scalaVersion()),
|
||||
ivy"com.lihaoyi::fansi:0.2.14",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package de.qwertyuiop.copret
|
|||
import de.qwertyuiop.copret.syntax._
|
||||
import ammonite.ops.{%%, pwd}
|
||||
|
||||
case class Theme(styles: Map[String, fansi.Attrs], figletFonts: Map[String, String]) {
|
||||
case class Theme(styles: Map[String, fansi.Attrs], figletFonts: Map[String, String]):
|
||||
def style(key: String, default: fansi.Attrs = fansi.Attrs()) =
|
||||
styles.getOrElse(key, default)
|
||||
|
||||
|
@ -14,17 +14,15 @@ case class Theme(styles: Map[String, fansi.Attrs], figletFonts: Map[String, Stri
|
|||
|
||||
def extend(newFonts: Map[String, String])(implicit d: DummyImplicit) = copy(figletFonts = figletFonts ++ newFonts)
|
||||
def ++(newFonts: Map[String, String])(implicit d: DummyImplicit) = copy(figletFonts = figletFonts ++ newFonts)
|
||||
}
|
||||
object Theme {
|
||||
implicit val default = Theme(Map(
|
||||
|
||||
given default: Theme = Theme(Map(
|
||||
"titleLine" -> (fansi.Bold.On ++ fansi.Color.DarkGray),
|
||||
"code" -> fansi.Color.Yellow
|
||||
),
|
||||
Map("titleLine" -> "pagga")
|
||||
)
|
||||
}
|
||||
|
||||
object Format {
|
||||
object Format:
|
||||
def alignRight(str: String, padding: Int = 2) =" " * (columns - str.length - padding) + str + " " * padding
|
||||
|
||||
def center(str: String) = " " * ((columns - str.length) / 2) + str
|
||||
|
@ -32,21 +30,18 @@ object Format {
|
|||
def figlet(str: String, font: String) = %%("figlet", "-t", "-f", font, str)(pwd).out.string
|
||||
|
||||
def centerLines(str: String) = str.split("\n").map(center).mkString("\n")
|
||||
def centerBlock(str: String) = {
|
||||
def centerBlock(str: String) =
|
||||
val lines = str.split("\n")
|
||||
val maxLen = lines.map(_.length).max
|
||||
val pad = " " * ((columns - maxLen) / 2)
|
||||
lines.map(pad + _).mkString("\n")
|
||||
}
|
||||
|
||||
def distribute(texts: String*) = {
|
||||
def distribute(texts: String*) =
|
||||
val totalPad = columns - texts.map(_.length).sum
|
||||
val numPads = texts.size - 1
|
||||
val pad = " " * (totalPad / numPads)
|
||||
texts.init.mkString(pad) + pad + " " * (totalPad % numPads) + texts.last
|
||||
}
|
||||
|
||||
private[copret] val ticks = raw"`([^`]*)`".r
|
||||
}
|
||||
|
||||
/* vim:set tw=120: */
|
||||
|
|
|
@ -2,29 +2,28 @@ package de.qwertyuiop.copret
|
|||
|
||||
import ammonite.ops.Path
|
||||
|
||||
sealed trait SlideAction
|
||||
case object Start extends SlideAction
|
||||
case class Goto(slideIndex: Int) extends SlideAction
|
||||
case object GotoSelect extends SlideAction
|
||||
case object Prev extends SlideAction
|
||||
case object Next extends SlideAction
|
||||
case object QuickNext extends SlideAction
|
||||
case object Quit extends SlideAction
|
||||
case class Interactive(cmd: Vector[String], wd: Path) extends SlideAction
|
||||
case class Other(code: List[Int]) extends SlideAction
|
||||
enum SlideAction:
|
||||
case Start
|
||||
case Goto(slideIndex: Int)
|
||||
case GotoSelect
|
||||
case Prev
|
||||
case Next
|
||||
case QuickNext
|
||||
case Quit
|
||||
case Interactive(cmd: Vector[String], wd: Path)
|
||||
case Other(code: List[Int])
|
||||
|
||||
object SlideAction {
|
||||
object SlideAction:
|
||||
def runForeground(cmd: String*)(implicit wd: Path) = Interactive(cmd.toVector, wd)
|
||||
}
|
||||
|
||||
import SlideAction.*
|
||||
|
||||
case class Keymap(bindings: Map[List[Int], SlideAction]) {
|
||||
case class Keymap(bindings: Map[List[Int], SlideAction]):
|
||||
def apply(keycode: List[Int]): SlideAction = bindings.getOrElse(keycode, Other(keycode))
|
||||
|
||||
def extend(newBindings: Map[List[Int], SlideAction]) = Keymap(bindings ++ newBindings)
|
||||
def ++(newBindings: Map[List[Int], SlideAction]) = Keymap(bindings ++ newBindings)
|
||||
}
|
||||
object Keymap {
|
||||
object Keymap:
|
||||
val empty = Keymap(Map())
|
||||
val default = Keymap(Map(
|
||||
Key.Up -> Prev,
|
||||
|
@ -41,46 +40,43 @@ object Keymap {
|
|||
Key('s') -> GotoSelect,
|
||||
))
|
||||
|
||||
}
|
||||
|
||||
object Key {
|
||||
object codes {
|
||||
object Key:
|
||||
object codes:
|
||||
val Esc = 27
|
||||
val Backspace = 127
|
||||
}
|
||||
val Esc = List(codes.Esc)
|
||||
val Backspace = List(codes.Backspace)
|
||||
val Delete = List(codes.Esc, '[', '3', '~')
|
||||
val Esc = List[Int](codes.Esc)
|
||||
val Backspace = List[Int](codes.Backspace)
|
||||
val Delete = List[Int](codes.Esc, '[', '3', '~')
|
||||
|
||||
val PageUp = List(codes.Esc, '[', '5', '~')
|
||||
val PageDown = List(codes.Esc, '[', '6', '~')
|
||||
val PageUp = List[Int](codes.Esc, '[', '5', '~')
|
||||
val PageDown = List[Int](codes.Esc, '[', '6', '~')
|
||||
|
||||
val Home = List(codes.Esc, '[', 'H')
|
||||
val End = List(codes.Esc, '[', 'F')
|
||||
val Home = List[Int](codes.Esc, '[', 'H')
|
||||
val End = List[Int](codes.Esc, '[', 'F')
|
||||
|
||||
val F1 = List(codes.Esc, 'P')
|
||||
val F2 = List(codes.Esc, 'Q')
|
||||
val F3 = List(codes.Esc, 'R')
|
||||
val F4 = List(codes.Esc, 'S')
|
||||
val F1 = List[Int](codes.Esc, 'P')
|
||||
val F2 = List[Int](codes.Esc, 'Q')
|
||||
val F3 = List[Int](codes.Esc, 'R')
|
||||
val F4 = List[Int](codes.Esc, 'S')
|
||||
|
||||
val F5 = List(codes.Esc, '1', '5', '~')
|
||||
val F6 = List(codes.Esc, '1', '7', '~')
|
||||
val F7 = List(codes.Esc, '1', '8', '~')
|
||||
val F8 = List(codes.Esc, '1', '9', '~')
|
||||
val F5 = List[Int](codes.Esc, '1', '5', '~')
|
||||
val F6 = List[Int](codes.Esc, '1', '7', '~')
|
||||
val F7 = List[Int](codes.Esc, '1', '8', '~')
|
||||
val F8 = List[Int](codes.Esc, '1', '9', '~')
|
||||
|
||||
val F9 = List(codes.Esc, '2', '0', '~')
|
||||
val F10 = List(codes.Esc, '2', '1', '~')
|
||||
val F11 = List(codes.Esc, '2', '3', '~')
|
||||
val F12 = List(codes.Esc, '2', '4', '~')
|
||||
val F9 = List[Int](codes.Esc, '2', '0', '~')
|
||||
val F10 = List[Int](codes.Esc, '2', '1', '~')
|
||||
val F11 = List[Int](codes.Esc, '2', '3', '~')
|
||||
val F12 = List[Int](codes.Esc, '2', '4', '~')
|
||||
|
||||
val Tab = List('\t')
|
||||
val Tab = List[Int]('\t')
|
||||
|
||||
val Up = List(codes.Esc, '[', 'A')
|
||||
val Down = List(codes.Esc, '[', 'B')
|
||||
val Right = List(codes.Esc, '[', 'C')
|
||||
val Left = List(codes.Esc, '[', 'D')
|
||||
val Up = List[Int](codes.Esc, '[', 'A')
|
||||
val Down = List[Int](codes.Esc, '[', 'B')
|
||||
val Right = List[Int](codes.Esc, '[', 'C')
|
||||
val Left = List[Int](codes.Esc, '[', 'D')
|
||||
|
||||
def apply(char: Char): List[Int] = List(char.toInt)
|
||||
}
|
||||
|
||||
/* vim:set tw=120: */
|
||||
|
|
|
@ -3,39 +3,40 @@ import ammonite.ops._
|
|||
import Terminal._
|
||||
import syntax._
|
||||
|
||||
case class Presentation(slides: Vector[Slide], meta: Map[String, String] = Map.empty) {
|
||||
def start(keymap: Keymap = Keymap.default) = {
|
||||
case class Presentation(slides: Vector[Slide], meta: Map[String, String] = Map.empty):
|
||||
def start(using keymap: Keymap = Keymap.default) =
|
||||
Terminal.enterRawMode()
|
||||
run(keymap)
|
||||
}
|
||||
run()
|
||||
|
||||
import Presentation._
|
||||
def run(implicit k: Keymap) = {
|
||||
@annotation.tailrec def rec(p: Presentation, pos: Int, action: SlideAction): Unit = {
|
||||
action match {
|
||||
def run()(using Keymap) =
|
||||
import SlideAction.*
|
||||
@annotation.tailrec def rec(p: Presentation, pos: Int, action: SlideAction): Unit =
|
||||
action match
|
||||
case Start =>
|
||||
executeSlide(p, pos)()
|
||||
rec(p, 1, waitkey)
|
||||
case Next | Other(_) =>
|
||||
if(pos + 1 < p.slides.size) {
|
||||
if pos + 1 < p.slides.size then
|
||||
executeSlide(p, pos + 1)()
|
||||
rec(p, pos + 1, waitkey)
|
||||
} else rec(p, pos, waitkey)
|
||||
else rec(p, pos, waitkey)
|
||||
case QuickNext =>
|
||||
if(pos + 1 < p.slides.size) {
|
||||
if pos + 1 < p.slides.size then
|
||||
executeQuick(p, pos + 1)()
|
||||
rec(p, pos + 1, waitkey)
|
||||
} else rec(p, pos, waitkey)
|
||||
else rec(p, pos, waitkey)
|
||||
case Prev =>
|
||||
if(pos > 0) {
|
||||
if pos > 0 then
|
||||
executeQuick(p, pos - 1)()
|
||||
rec(p, pos - 1, waitkey)
|
||||
} else rec(p, pos, waitkey)
|
||||
else rec(p, pos, waitkey)
|
||||
case Interactive(cmd, path) =>
|
||||
%(cmd)(path)
|
||||
rec(p, pos - 1, QuickNext)
|
||||
case Goto(target) =>
|
||||
for (i <- 0 until target) executeSilent(p, i)()
|
||||
for i <- 0 until target
|
||||
do executeSilent(p, i)()
|
||||
rec(p, target - 1, QuickNext)
|
||||
case GotoSelect =>
|
||||
val maxSlide = p.slides.size - 1
|
||||
|
@ -43,21 +44,18 @@ case class Presentation(slides: Vector[Slide], meta: Map[String, String] = Map.e
|
|||
(res, input) => res.filter((0 to maxSlide).contains).isEmpty && input.nonEmpty,
|
||||
in => s"No such slide: $in (empty input to abort)"
|
||||
)
|
||||
target match {
|
||||
target match
|
||||
case Some(i) => rec(p, pos, Goto(i))
|
||||
case None => rec(p, pos - 1, QuickNext)
|
||||
}
|
||||
case Quit => ()
|
||||
}
|
||||
}
|
||||
rec(this, 0, Start)
|
||||
}
|
||||
}
|
||||
object Presentation {
|
||||
def executeSlide(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match {
|
||||
|
||||
|
||||
object Presentation:
|
||||
def executeSlide(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match
|
||||
case Paragraph(contents) => println(contents)
|
||||
case Clear => print("\u001b[2J\u001b[;H")
|
||||
case PauseKey => waitkey(Keymap.empty)
|
||||
case PauseKey => waitkey(using Keymap.empty)
|
||||
case Pause(msec) => Thread.sleep(msec)
|
||||
case incMd @ IncludeMarkdown(_) => println(incMd.markdownBlock())
|
||||
case Image(file, width, height, keepAspect) => print(Terminal.showImage(file, width, height, keepAspect))
|
||||
|
@ -66,125 +64,114 @@ object Presentation {
|
|||
case Group(slides) => slides.foreach(executeSlide(p, pos))
|
||||
case lios @ LazyIOSlide(_, display) => executeSlide(p, pos)(lios.genSlide())
|
||||
case Meta(genSlide) => executeSlide(p, pos)(genSlide(p, pos))
|
||||
case other => println("Error: Unknown slide type:"); println(other)
|
||||
}
|
||||
|
||||
def executeQuick(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match {
|
||||
def executeQuick(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match
|
||||
case Pause(msec) => ()
|
||||
case PauseKey => ()
|
||||
case cmd: TypedCommand[_] => cmd.quickShow()
|
||||
case Group(slides) => slides.foreach(executeQuick(p, pos))
|
||||
case lios @ LazyIOSlide(_, display) => executeQuick(p, pos)(lios.genSlide())
|
||||
case _ => executeSlide(p, pos)(slide)
|
||||
}
|
||||
|
||||
def executeSilent(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match {
|
||||
def executeSilent(p: Presentation, pos: Int)(slide: Slide = p.slides(pos)): Unit = slide match
|
||||
case cmd: TypedCommand[_] => cmd.force()
|
||||
case Group(slides) => slides.foreach(executeSilent(p, pos))
|
||||
case lios @ LazyIOSlide(_, display) => executeSilent(p, pos)(lios.genSlide())
|
||||
case Paragraph(_) | Image(_,_,_,_) | Clear | IncludeMarkdown(_) | Meta(_) => ()
|
||||
case _ => executeQuick(p, pos)(slide)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sealed trait Slide
|
||||
case class Paragraph(contents: fansi.Str) extends Slide
|
||||
case class IncludeMarkdown(path: Path) extends Slide {
|
||||
def markdownBlock() = %%%("/usr/bin/mdcat", "--columns", (columns * 0.8).toInt.toString, path.toString)(pwd).block
|
||||
}
|
||||
case class IncludeMarkdown(path: Path) extends Slide:
|
||||
def markdownBlock() =
|
||||
%%%("/usr/bin/mdcat", "--columns", (columns * 0.8).toInt.toString, path.toString)(using ImplicitWd.implicitCwd).block
|
||||
case class Image(path: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) extends Slide
|
||||
case object Clear extends Slide
|
||||
case class Pause(millisec: Long) extends Slide
|
||||
case object PauseKey extends Slide
|
||||
case class Meta(contents: (Presentation, Int) => Slide) extends Slide
|
||||
|
||||
|
||||
case class TypedCommand[T](exec: T => String, display: String, cmd: T) extends Slide {
|
||||
case class TypedCommand[T](exec: T => String, display: String, cmd: T) extends Slide:
|
||||
private lazy val _output = exec(cmd)
|
||||
def output = _output
|
||||
def display(s: String): TypedCommand[T] = TypedCommand(exec, s, cmd)
|
||||
|
||||
def show() = {
|
||||
infix def showing (s: String): TypedCommand[T] = TypedCommand(exec, s, cmd)
|
||||
|
||||
def show() =
|
||||
prompt()
|
||||
typeCmd()
|
||||
print(output)
|
||||
}
|
||||
|
||||
def quickShow() = {
|
||||
def quickShow() =
|
||||
prompt()
|
||||
println(display)
|
||||
print(output)
|
||||
}
|
||||
|
||||
def prompt() = print(fansi.Color.LightGreen("user@host % "))
|
||||
def force() = _output
|
||||
|
||||
private def typeCmd() = {
|
||||
for (char <- display) {
|
||||
print(char)
|
||||
Thread.sleep(50 + scala.util.Random.nextInt(80))
|
||||
}
|
||||
private def typeCmd() =
|
||||
for char <- display
|
||||
do print(char)
|
||||
Thread.sleep(50 + scala.util.Random.nextInt(80))
|
||||
println()
|
||||
}
|
||||
|
||||
/* Conditionally disable execution. Useful for e.g. a debug mode, or a non-interactive mode */
|
||||
def disable(altDisplay: String = display, condition: Boolean = true) =
|
||||
if(condition) copy(display = altDisplay, exec = (_:T) => "")
|
||||
if condition then copy(display = altDisplay, exec = (_:T) => "")
|
||||
else this
|
||||
|
||||
/* Conditionally replace the executed command (but still displaying the same). Useful for e.g. a non-interactive mode,
|
||||
* where a call to an editor is replaced with a file operation */
|
||||
def replaceIf(condition: Boolean)(tc: TypedCommand[_]): TypedCommand[_] =
|
||||
if(condition) tc.display(display)
|
||||
if condition then tc.showing(display)
|
||||
else this
|
||||
|
||||
}
|
||||
|
||||
object TypedCommand {
|
||||
object TypedCommand:
|
||||
val shell = sys.env.getOrElse("SHELL", "sh")
|
||||
|
||||
def run(implicit wd: Path): Vector[String] => String =
|
||||
def run(using Path): Vector[String] => String =
|
||||
c => safe_%%(c)
|
||||
|
||||
def runShell(implicit wd: Path): Vector[String] => String =
|
||||
def runShell(using Path): Vector[String] => String =
|
||||
c => safe_%%(Vector(shell, "-c", c.mkString(" ")))
|
||||
|
||||
def runInteractive(implicit wd: Path): Vector[String] => String =
|
||||
def runInteractive(using Path): Vector[String] => String =
|
||||
c => { %(c); ""}
|
||||
|
||||
def apply(cmd: String*)(implicit wd: Path): TypedCommand[Vector[String]] =
|
||||
def apply(cmd: String*)(using Path): TypedCommand[Vector[String]] =
|
||||
TypedCommand(run, cmd.mkString(" "), cmd.toVector)
|
||||
|
||||
def shell(cmd: String*)(implicit wd: Path): TypedCommand[Vector[String]] =
|
||||
def shell(cmd: String*)(using Path): TypedCommand[Vector[String]] =
|
||||
TypedCommand(runShell, cmd.mkString(" "), cmd.toVector)
|
||||
|
||||
def fake(cmd: String): TypedCommand[String] =
|
||||
TypedCommand(_ => "", cmd, cmd)
|
||||
|
||||
def interactive(cmd: String*)(implicit wd: Path): TypedCommand[Vector[String]] =
|
||||
def interactive(cmd: String*)(using Path): TypedCommand[Vector[String]] =
|
||||
TypedCommand(runInteractive, cmd.mkString(" "), cmd.toVector)
|
||||
}
|
||||
|
||||
|
||||
sealed abstract case class Silent[T] private (doStuff: () => T) extends Slide
|
||||
object Silent { def apply[T](doStuff: => T) = new Silent(() => doStuff){} }
|
||||
object Silent:
|
||||
def apply[T](doStuff: => T) = new Silent(() => doStuff){}
|
||||
|
||||
|
||||
case class Group(slides: List[Slide]) extends Slide
|
||||
object Group { def apply(slides: Slide*): Group = Group(slides.toList) }
|
||||
object Group:
|
||||
def apply(slides: Slide*): Group = Group(slides.toList)
|
||||
|
||||
case class LazyIOSlide[T](runOnce: () => T, display: T => Slide) extends Slide {
|
||||
case class LazyIOSlide[T](runOnce: () => T, display: T => Slide) extends Slide:
|
||||
private lazy val data = runOnce()
|
||||
def genSlide(): Slide = display(data)
|
||||
}
|
||||
|
||||
trait SlideSyntax {
|
||||
private[copret] class LazyIOSlideBuilder[T](runOnce: => T) {
|
||||
trait SlideSyntax:
|
||||
private[copret] class LazyIOSlideBuilder[T](runOnce: => T):
|
||||
def useIn(display: T => Slide) = LazyIOSlide(() => runOnce, display)
|
||||
}
|
||||
|
||||
def prepare[T](runOnce: => T): LazyIOSlideBuilder[T] = new LazyIOSlideBuilder(runOnce)
|
||||
}
|
||||
def prepare[T](runOnce: => T): LazyIOSlideBuilder[T] = LazyIOSlideBuilder(runOnce)
|
||||
|
||||
|
||||
/* vim:set tw=120: */
|
||||
|
|
|
@ -1,38 +1,33 @@
|
|||
package de.qwertyuiop.copret
|
||||
|
||||
object syntax extends Templates with TerminalSyntax with SlideSyntax {
|
||||
implicit class PresenterStringExtensions(val str: String) {
|
||||
import Format._
|
||||
def code(implicit theme: Theme) = Format.ticks.replaceAllIn(str, m => theme.style("code")("$1").render)
|
||||
def text(implicit theme: Theme) = Paragraph(str)
|
||||
def par(implicit theme: Theme) = Paragraph(str.stripMargin.code.padLeft(2))
|
||||
def style(key: String, default: fansi.Attrs = fansi.Attrs())(implicit theme: Theme) = theme.style(key, default)(str)
|
||||
object syntax extends Templates with TerminalSyntax with SlideSyntax:
|
||||
import Format._
|
||||
extension (str: String)
|
||||
def code(using theme: Theme) = Format.ticks.replaceAllIn(str, m => theme.style("code")("$1").render)
|
||||
def text(using Theme) = Paragraph(str)
|
||||
def par(using Theme) = Paragraph(str.stripMargin.code.padLeft(2))
|
||||
def style(key: String, default: fansi.Attrs)(using theme: Theme) = theme.style(key, default)(str)
|
||||
|
||||
def centered = center(str)
|
||||
def block = centerBlock(str)
|
||||
def right = alignRight(str)
|
||||
def right(padding: Int) = alignRight(str, padding)
|
||||
def padLeft(padding: Int) = {
|
||||
def padLeft(padding: Int) =
|
||||
val pad = " " * padding
|
||||
str.linesIterator.map(pad + _).mkString("\n")
|
||||
}
|
||||
|
||||
def blue = fansi.Color.Blue(str)
|
||||
def green = fansi.Color.Green(str)
|
||||
def yellow = fansi.Color.Yellow(str)
|
||||
def red = fansi.Color.Red(str)
|
||||
}
|
||||
|
||||
implicit class PresenterFansiStringExtensions(val str: fansi.Str) {
|
||||
import Format._
|
||||
def text(implicit theme: Theme) = Paragraph(str)
|
||||
def style(key: String, default: fansi.Attrs = fansi.Attrs())(implicit theme: Theme) = theme.style(key, default)(str)
|
||||
extension (str: fansi.Str)
|
||||
def text(using Theme) = Paragraph(str)
|
||||
def style(key: String, default: fansi.Attrs)(using theme: Theme) = theme.style(key, default)(str)
|
||||
|
||||
def blue = fansi.Color.Blue(str)
|
||||
def green = fansi.Color.Green(str)
|
||||
def yellow = fansi.Color.Yellow(str)
|
||||
def red = fansi.Color.Red(str)
|
||||
}
|
||||
|
||||
}
|
||||
/* vim:set tw=120: */
|
||||
|
|
|
@ -2,7 +2,7 @@ package de.qwertyuiop.copret
|
|||
import syntax._
|
||||
import ammonite.ops.{Path, %, %%, pwd}
|
||||
|
||||
trait Templates {
|
||||
trait Templates:
|
||||
def titleLine(title: String)(implicit theme: Theme) = Paragraph(
|
||||
"\n" + Format.figlet(title, theme.font("titleLine", "pagga")).block.blue + "\n"
|
||||
)
|
||||
|
@ -20,6 +20,5 @@ trait Templates {
|
|||
def markdown(title: String, content: Path) = slide(title)(IncludeMarkdown(content))
|
||||
|
||||
lazy val --- = Paragraph(("═" * columns).yellow)
|
||||
}
|
||||
|
||||
/* vim:set tw=120: */
|
||||
|
|
|
@ -3,70 +3,69 @@ import ammonite.ops.{Path, ShelloutException, pwd, read, %, %%}
|
|||
import org.jline.terminal.TerminalBuilder
|
||||
import org.jline.reader.LineReaderBuilder
|
||||
|
||||
object Terminal {
|
||||
def safe_%%(cmd: Vector[String])(implicit wd: Path): String =
|
||||
try {
|
||||
object Terminal:
|
||||
def safe_%%(cmd: Vector[String])(using Path): String =
|
||||
try
|
||||
%%(cmd).out.string
|
||||
} catch {
|
||||
catch
|
||||
case e: ShelloutException => e.result.err.string
|
||||
}
|
||||
|
||||
|
||||
def tryCmd[T](cmd: => T, default: => T) =
|
||||
try { cmd } catch { case e: ShelloutException => default }
|
||||
|
||||
def enterRawMode(): Unit = {
|
||||
def enterRawMode(): Unit =
|
||||
%%("sh", "-c", "stty -icanon min 1 < /dev/tty")(pwd)
|
||||
%%("sh", "-c", "stty -echo < /dev/tty")(pwd)
|
||||
}
|
||||
|
||||
private[copret] lazy val jterm = org.jline.terminal.TerminalBuilder.terminal()
|
||||
private[copret] lazy val lineReader = LineReaderBuilder.builder().terminal(jterm).build()
|
||||
|
||||
def waitkey(implicit keymap: Keymap): SlideAction = {
|
||||
def waitkey(using keymap: Keymap): SlideAction =
|
||||
// ignore keypresses done during slide animations
|
||||
while(Console.in.ready()) Console.in.read
|
||||
while Console.in.ready() do Console.in.read
|
||||
|
||||
var key = scala.collection.mutable.ArrayBuffer[Int]()
|
||||
key += Console.in.read
|
||||
while(Console.in.ready)
|
||||
while Console.in.ready do
|
||||
key += Console.in.read
|
||||
keymap(key.toList)
|
||||
}
|
||||
|
||||
def prompt[T](prefix: String, parse: String => T)(
|
||||
retry: (T, String) => Boolean = (t: T, s: String) => false,
|
||||
error: String => String = in => s"Invalid input: $in"
|
||||
): T = {
|
||||
): T =
|
||||
val input = lineReader.readLine(prefix + " ")
|
||||
val result = parse(input)
|
||||
if(retry(result, input)) {
|
||||
if retry(result, input) then
|
||||
println(error(input))
|
||||
prompt(prefix, parse)(retry, error)
|
||||
}
|
||||
else result
|
||||
}
|
||||
|
||||
def isTmux = sys.env.contains("TMUX") || sys.env("TERM").startsWith("screen")
|
||||
val term = sys.env("TERM")
|
||||
|
||||
def osc = if (isTmux) "\u001bPtmux\u001b\u001b]" else "\u001b]"
|
||||
def st = if (isTmux) "\u0007\u001b\\" else "\u0007"
|
||||
def isTmux = sys.env.contains("TMUX") || term.startsWith("screen")
|
||||
|
||||
def showImage(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) = {
|
||||
def osc = if isTmux then "\u001bPtmux\u001b\u001b]" else "\u001b]"
|
||||
def st = if isTmux then "\u0007\u001b\\" else "\u0007"
|
||||
|
||||
def showImage(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) =
|
||||
if term == "xterm-kitty" then showImageKitty(img)
|
||||
else showImageIterm(img, width, height, keepAspect)
|
||||
|
||||
def showImageIterm(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) =
|
||||
import java.util.Base64
|
||||
val image = Base64.getEncoder.encodeToString(read.bytes(img))
|
||||
val aspect = if(keepAspect) 1 else 0
|
||||
val aspect = if keepAspect then 1 else 0
|
||||
s"${osc}1337;File=inline=1;width=$width;height=$height;preserveAspectRatio=$aspect:$image$st"
|
||||
}
|
||||
}
|
||||
|
||||
private[copret] trait TerminalSyntax {
|
||||
def showImageKitty(img: Path, width: String = "100%", height: String = "100%", keepAspect: Boolean = true) =
|
||||
import java.util.Base64
|
||||
s"\u001b_Gf=100,t=f,a=T;${Base64.getEncoder.encodeToString(img.toString.toCharArray.map(_.toByte))}\u001b\\"
|
||||
|
||||
private[copret] trait TerminalSyntax:
|
||||
import Terminal._
|
||||
|
||||
def %%%(cmd: String*)(implicit wd: Path) = safe_%%(cmd.toVector)
|
||||
def %%%(cmd: String*)(using Path) = safe_%%(cmd.toVector)
|
||||
def columns = jterm.getSize.getColumns
|
||||
def rows = jterm.getSize.getRows
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* vim:set tw=120: */
|
||||
|
|
|
@ -4,7 +4,8 @@ import de.qwertyuiop.copret._
|
|||
import de.qwertyuiop.copret.syntax._
|
||||
import TypedCommand.{interactive, shell => sh}, Format.figlet
|
||||
|
||||
import ammonite.ops._
|
||||
import ammonite.ops.{given, *}
|
||||
|
||||
|
||||
/* Configuration */
|
||||
|
||||
|
@ -12,8 +13,8 @@ import ammonite.ops._
|
|||
* most of the commands in this presentation will be git commands in a demo repository, so the path to that repo is set
|
||||
* as implicit (will be the working directory of executed commands) */
|
||||
val imgs = pwd/"img"
|
||||
implicit val repoDir = pwd/"demoRepo"
|
||||
implicit val theme = Theme.default
|
||||
given repoDir: Path= pwd/"demoRepo"
|
||||
import de.qwertyuiop.copret.{given Theme}
|
||||
|
||||
/* You can define any variables and use then in your presentation.
|
||||
* The presentation is pure Scala code, you can use anything that Scala offers . */
|
||||
|
@ -57,7 +58,7 @@ def chapter(title1: String, title2: String, subtitle: String): Group = {
|
|||
Group(Clear,
|
||||
header, // a built in template
|
||||
Paragraph(
|
||||
figlet(title1, font).block.green ++ figlet(title2, font).block.green + "\n" + subtitle.right(10).green
|
||||
figlet(title1, font).block.green ++ figlet(title2, font).block.green ++ "\n" ++ subtitle.right(10).green
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -91,7 +92,7 @@ def gitCatFile(ref: String) = TypedCommand("git", "cat-file", "-p", ref)
|
|||
def gitLogGraph(cutOff: Int = 0) = {
|
||||
val shownCmd = "git log --graph --oneline --all"
|
||||
val execCmd = shownCmd + " --decorate=short --color=always " + (if(cutOff > 0) "| head -n -" + cutOff else "")
|
||||
val typer = sh(execCmd) display shownCmd
|
||||
val typer = sh(execCmd) showing shownCmd
|
||||
if(cutOff > 0)
|
||||
Group(typer, "...".text)
|
||||
else
|
||||
|
@ -133,10 +134,10 @@ val presentation = Presentation(Vector(
|
|||
Pause(500),
|
||||
/* `TypedCommand.shell` (here aliased to `sh`) displays a typing animation of that command and then executes it,
|
||||
* showing its output in the presentation */
|
||||
sh("git init demoRepo")(pwd),
|
||||
sh("git init demoRepo")(using pwd),
|
||||
/* sometimes it's useful to display something else than is actually executed, e.g. to add comments, or to hide
|
||||
* options only required because we don't call from an interactive shell (stuff like --color) */
|
||||
sh("ls -1a --color=always demoRepo")(pwd) display "ls -1a demoRepo",
|
||||
sh("ls -1a --color=always demoRepo")(using pwd) showing "ls -1a demoRepo",
|
||||
/* If you need to run commands that should not display anything in the presentation, use `Silent`.
|
||||
* Here I use it to prepare the repository, but it could also be used e.g. for playing a sound or opening a video.*/
|
||||
Silent {
|
||||
|
@ -307,13 +308,13 @@ val presentation = Presentation(Vector(
|
|||
slide("Branches")(
|
||||
"Machen wir ein paar Änderungen und committen sie:\n".par,
|
||||
---,
|
||||
sh("echo 'a new line' >> hello.txt") display "echo 'a new line' >> hello.txt # appends to hello.txt",
|
||||
sh("echo 'a new line' >> hello.txt") showing "echo 'a new line' >> hello.txt # appends to hello.txt",
|
||||
sh("git add hello.txt"),
|
||||
sh("git commit -m \"Added line to hello\""),
|
||||
---,
|
||||
"Da der Branch `feature/foo` aktiv war, zeigt dieser auf den neuen Commit. `master` wird nicht geändert:".code.text,
|
||||
PauseKey,
|
||||
sh("git log --graph --oneline --all --decorate=short --color=always") display "git log --graph --oneline --all",
|
||||
sh("git log --graph --oneline --all --decorate=short --color=always") showing "git log --graph --oneline --all",
|
||||
),
|
||||
slide("Branches")(
|
||||
"Jetzt wechseln wir zurück zu `master`:\n".par,
|
||||
|
@ -357,7 +358,7 @@ val presentation = Presentation(Vector(
|
|||
"""Git speichert Objekte unter `.git/objects/<erste zwei Stellen vom Hash>/<Rest vom Hash>`
|
||||
|Objekte sind komprimiert.
|
||||
|""".par,
|
||||
sh("tree -C .git/objects/ | head -n 11") display "tree .git/objects/",
|
||||
sh("tree -C .git/objects/ | head -n 11") showing "tree .git/objects/",
|
||||
s"...".par,
|
||||
),
|
||||
), meta=meta)
|
||||
|
@ -366,7 +367,7 @@ val presentation = Presentation(Vector(
|
|||
* The `runForeground` action lets you run any command, interrupting the presentation until it exits.
|
||||
* Here I bind the 'i' key to open tmux in the demo repo, for having interactive access, so I can call additional git
|
||||
* commands for questions */
|
||||
presentation.start(Keymap.default ++ Map(
|
||||
presentation.start(using Keymap.default ++ Map(
|
||||
Key('i') -> SlideAction.runForeground("tmux"),
|
||||
))
|
||||
|
||||
|
|
Loading…
Reference in a new issue