Move presentation tooling to its own repo
This commit is contained in:
commit
3ea943b1c4
35
3rd-party-licenses/jline3.LICENSE.txt
Normal file
35
3rd-party-licenses/jline3.LICENSE.txt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
Copyright (c) 2002-2018, the original author or authors.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
https://opensource.org/licenses/BSD-3-Clause
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or
|
||||||
|
without modification, are permitted provided that the following
|
||||||
|
conditions are met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with
|
||||||
|
the distribution.
|
||||||
|
|
||||||
|
Neither the name of JLine nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this
|
||||||
|
software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||||
|
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||||
|
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||||
|
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
26
build.sc
Normal file
26
build.sc
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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 publishVersion = "0.0.1"
|
||||||
|
def pomSettings = PomSettings(
|
||||||
|
description = "Use ammonite scripts for command line presentations",
|
||||||
|
organization = "de.qwertyuiop",
|
||||||
|
versionControl = VersionControl.github("crater2150", "copret"),
|
||||||
|
url = "https://qwertyuiop.de/copret/",
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
52
copret/src/Theme.scala
Normal file
52
copret/src/Theme.scala
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
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]) {
|
||||||
|
def style(key: String, default: fansi.Attrs = fansi.Attrs()) =
|
||||||
|
styles.getOrElse(key, default)
|
||||||
|
|
||||||
|
def font(key: String, default: String) =
|
||||||
|
figletFonts.getOrElse(key, default)
|
||||||
|
|
||||||
|
def extend(newStyles: Map[String, fansi.Attrs]) = copy(styles = styles ++ newStyles)
|
||||||
|
def ++(newStyles: Map[String, fansi.Attrs]) = copy(styles = styles ++ newStyles)
|
||||||
|
|
||||||
|
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(
|
||||||
|
"titleLine" -> (fansi.Bold.On ++ fansi.Color.DarkGray),
|
||||||
|
"code" -> fansi.Color.Yellow
|
||||||
|
),
|
||||||
|
Map("titleLine" -> "pagga")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object Format {
|
||||||
|
def alignRight(str: String, padding: Int = 2) =" " * (columns - str.length - padding) + str + " " * padding
|
||||||
|
|
||||||
|
def center(str: String) = " " * ((columns - str.length) / 2) + str
|
||||||
|
|
||||||
|
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) = {
|
||||||
|
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*) = {
|
||||||
|
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: */
|
86
copret/src/keys.scala
Normal file
86
copret/src/keys.scala
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
object SlideAction {
|
||||||
|
def runForeground(cmd: String*)(implicit wd: Path) = Interactive(cmd.toVector, wd)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 {
|
||||||
|
val empty = Keymap(Map())
|
||||||
|
val default = Keymap(Map(
|
||||||
|
Key.Up -> Prev,
|
||||||
|
Key.Left -> Prev,
|
||||||
|
Key.PageUp -> Prev,
|
||||||
|
Key('k') -> Prev,
|
||||||
|
Key(' ') -> Next,
|
||||||
|
Key('j') -> Next,
|
||||||
|
Key.Down -> QuickNext,
|
||||||
|
Key.Right -> QuickNext,
|
||||||
|
Key.PageDown -> QuickNext,
|
||||||
|
Key('q') -> Quit,
|
||||||
|
Key('g') -> Start,
|
||||||
|
Key('s') -> GotoSelect,
|
||||||
|
))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 PageUp = List(codes.Esc, '[', '5', '~')
|
||||||
|
val PageDown = List(codes.Esc, '[', '6', '~')
|
||||||
|
|
||||||
|
val Home = List(codes.Esc, '[', 'H')
|
||||||
|
val End = List(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 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 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 Tab = List('\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')
|
||||||
|
|
||||||
|
def apply(char: Char): List[Int] = List(char.toInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim:set tw=120: */
|
192
copret/src/slides.scala
Normal file
192
copret/src/slides.scala
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package de.qwertyuiop.copret
|
||||||
|
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) = {
|
||||||
|
Terminal.enterRawMode()
|
||||||
|
run(keymap)
|
||||||
|
}
|
||||||
|
|
||||||
|
import Presentation._
|
||||||
|
def run(implicit k: Keymap) = {
|
||||||
|
@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) {
|
||||||
|
executeSlide(p, pos + 1)()
|
||||||
|
rec(p, pos + 1, waitkey)
|
||||||
|
} else rec(p, pos, waitkey)
|
||||||
|
case QuickNext =>
|
||||||
|
if(pos + 1 < p.slides.size) {
|
||||||
|
executeQuick(p, pos + 1)()
|
||||||
|
rec(p, pos + 1, waitkey)
|
||||||
|
} else rec(p, pos, waitkey)
|
||||||
|
case Prev =>
|
||||||
|
if(pos > 0) {
|
||||||
|
executeQuick(p, pos - 1)()
|
||||||
|
rec(p, pos - 1, 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)()
|
||||||
|
rec(p, target - 1, QuickNext)
|
||||||
|
case GotoSelect =>
|
||||||
|
val maxSlide = p.slides.size - 1
|
||||||
|
val target = prompt(s"Go to slide (0 - $maxSlide):", _.toIntOption)(
|
||||||
|
(res, input) => res.filter((0 to maxSlide).contains).isEmpty && input.nonEmpty,
|
||||||
|
in => s"No such slide: $in (empty input to abort)"
|
||||||
|
)
|
||||||
|
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 {
|
||||||
|
case Paragraph(contents) => println(contents)
|
||||||
|
case Clear => print("\u001b[2J\u001b[;H")
|
||||||
|
case PauseKey => waitkey(Keymap.empty)
|
||||||
|
case Pause(msec) => Thread.sleep(msec)
|
||||||
|
case incMd @ IncludeMarkdown(_) => println(incMd.markdownBlock())
|
||||||
|
case Image(file) =>
|
||||||
|
try { %("imgcat", file.toString)(pwd) }
|
||||||
|
catch { case _: InteractiveShelloutException => println(s"Image missing: $file") }
|
||||||
|
case cmd: TypedCommand[_] => cmd.show()
|
||||||
|
case Silent(actions) => actions()
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 Image(path: Path) 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 {
|
||||||
|
private lazy val _output = exec(cmd)
|
||||||
|
def output = _output
|
||||||
|
def display(s: String): TypedCommand[T] = TypedCommand(exec, s, cmd)
|
||||||
|
|
||||||
|
def show() = {
|
||||||
|
prompt()
|
||||||
|
typeCmd()
|
||||||
|
print(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
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) => "")
|
||||||
|
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)
|
||||||
|
else this
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object TypedCommand {
|
||||||
|
val shell = sys.env.getOrElse("SHELL", "sh")
|
||||||
|
|
||||||
|
def run(implicit wd: Path): Vector[String] => String =
|
||||||
|
c => safe_%%(c)
|
||||||
|
|
||||||
|
def runShell(implicit wd: Path): Vector[String] => String =
|
||||||
|
c => safe_%%(Vector(shell, "-c", c.mkString(" ")))
|
||||||
|
|
||||||
|
def runInteractive(implicit wd: Path): Vector[String] => String =
|
||||||
|
c => { %(c); ""}
|
||||||
|
|
||||||
|
def apply(cmd: String*)(implicit wd: Path): TypedCommand[Vector[String]] =
|
||||||
|
TypedCommand(run, cmd.mkString(" "), cmd.toVector)
|
||||||
|
|
||||||
|
def shell(cmd: String*)(implicit wd: 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]] =
|
||||||
|
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){} }
|
||||||
|
|
||||||
|
|
||||||
|
case class Group(slides: List[Slide]) extends Slide
|
||||||
|
object Group { def apply(slides: Slide*): Group = Group(slides.toList) }
|
||||||
|
|
||||||
|
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) {
|
||||||
|
def useIn(display: T => Slide) = LazyIOSlide(() => runOnce, display)
|
||||||
|
}
|
||||||
|
|
||||||
|
def prepare[T](runOnce: => T): LazyIOSlideBuilder[T] = new LazyIOSlideBuilder(runOnce)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* vim:set tw=120: */
|
38
copret/src/syntax.scala
Normal file
38
copret/src/syntax.scala
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
def centered = center(str)
|
||||||
|
def block = centerBlock(str)
|
||||||
|
def right = alignRight(str)
|
||||||
|
def right(padding: Int) = alignRight(str, padding)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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: */
|
25
copret/src/templates.scala
Normal file
25
copret/src/templates.scala
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package de.qwertyuiop.copret
|
||||||
|
import syntax._
|
||||||
|
import ammonite.ops.{Path, %, %%, pwd}
|
||||||
|
|
||||||
|
trait Templates {
|
||||||
|
def titleLine(title: String)(implicit theme: Theme) = Paragraph(
|
||||||
|
"\n" + Format.figlet(title, theme.font("titleLine", "pagga")).block.blue + "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
def header(implicit theme: Theme) = Meta((p, pos) => {
|
||||||
|
val left = p.meta.getOrElse("author", "")
|
||||||
|
val center = p.meta.getOrElse("title", "")
|
||||||
|
val right = s"${pos} / ${p.slides.size - 1}"
|
||||||
|
theme.style("titleLine")(Format.distribute(left, center, right)).text
|
||||||
|
})
|
||||||
|
|
||||||
|
def slide(title: String)(slides: Slide*) = Group(Clear :: header :: titleLine(title) :: slides.toList)
|
||||||
|
def slide(slides: Slide*) = Group(Clear :: header :: slides.toList)
|
||||||
|
|
||||||
|
def markdown(title: String, content: Path) = slide(title)(IncludeMarkdown(content))
|
||||||
|
|
||||||
|
lazy val --- = Paragraph(("═" * columns).yellow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim:set tw=120: */
|
60
copret/src/terminal.scala
Normal file
60
copret/src/terminal.scala
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package de.qwertyuiop.copret
|
||||||
|
import ammonite.ops.{Path, ShelloutException, pwd, %, %%}
|
||||||
|
import org.jline.terminal.TerminalBuilder
|
||||||
|
import org.jline.reader.LineReaderBuilder
|
||||||
|
|
||||||
|
object Terminal {
|
||||||
|
def safe_%%(cmd: Vector[String])(implicit wd: Path): String =
|
||||||
|
try {
|
||||||
|
%%(cmd).out.string
|
||||||
|
} 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 = {
|
||||||
|
%%("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 = {
|
||||||
|
// ignore keypresses done during slide animations
|
||||||
|
while(Console.in.ready()) Console.in.read
|
||||||
|
|
||||||
|
var key = scala.collection.mutable.ArrayBuffer[Int]()
|
||||||
|
key += Console.in.read
|
||||||
|
while(Console.in.ready)
|
||||||
|
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 = {
|
||||||
|
val input = lineReader.readLine(prefix + " ")
|
||||||
|
val result = parse(input)
|
||||||
|
if(retry(result, input)) {
|
||||||
|
println(error(input))
|
||||||
|
prompt(prefix, parse)(retry, error)
|
||||||
|
}
|
||||||
|
else result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[copret] trait TerminalSyntax {
|
||||||
|
import Terminal._
|
||||||
|
|
||||||
|
def %%%(cmd: String*)(implicit wd: Path) = safe_%%(cmd.toVector)
|
||||||
|
def columns = jterm.getSize.getColumns
|
||||||
|
def rows = jterm.getSize.getRows
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim:set tw=120: */
|
48
mill
Executable file
48
mill
Executable file
|
@ -0,0 +1,48 @@
|
||||||
|
#!/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 env variable
|
||||||
|
# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION
|
||||||
|
DEFAULT_MILL_VERSION=0.9.5
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -z "$MILL_VERSION" ] ; then
|
||||||
|
if [ -f ".mill-version" ] ; then
|
||||||
|
MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)"
|
||||||
|
elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then
|
||||||
|
MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2)
|
||||||
|
else
|
||||||
|
MILL_VERSION=$DEFAULT_MILL_VERSION
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "x${XDG_CACHE_HOME}" != "x" ] ; then
|
||||||
|
MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download"
|
||||||
|
else
|
||||||
|
MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download"
|
||||||
|
fi
|
||||||
|
MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}"
|
||||||
|
|
||||||
|
version_remainder="$MILL_VERSION"
|
||||||
|
MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}"
|
||||||
|
MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}"
|
||||||
|
|
||||||
|
if [ ! -x "$MILL_EXEC_PATH" ] ; then
|
||||||
|
mkdir -p $MILL_DOWNLOAD_PATH
|
||||||
|
if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then
|
||||||
|
ASSEMBLY="-assembly"
|
||||||
|
fi
|
||||||
|
DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download
|
||||||
|
MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION${ASSEMBLY}"
|
||||||
|
curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL"
|
||||||
|
chmod +x "$DOWNLOAD_FILE"
|
||||||
|
mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH"
|
||||||
|
unset DOWNLOAD_FILE
|
||||||
|
unset MILL_DOWNLOAD_URL
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset MILL_DOWNLOAD_PATH
|
||||||
|
unset MILL_VERSION
|
||||||
|
|
||||||
|
exec $MILL_EXEC_PATH "$@"
|
Loading…
Reference in a new issue