Skip to content

Instantly share code, notes, and snippets.

@TonioGela
Created May 6, 2024 09:48
Show Gist options
  • Save TonioGela/8f74b697ee924500c19e78e8b6369b61 to your computer and use it in GitHub Desktop.
Save TonioGela/8f74b697ee924500c19e78e8b6369b61 to your computer and use it in GitHub Desktop.
Dotfiles persisting script launched by a launchd service (actually the launchd service is bugged, as it continuosly runs the script)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Disabled</key>
<false/>
<key>Label</key>
<string>Save dotfiles at every change</string>
<key>ProgramArguments</key>
<array>
<string>/Users/toniogela/Library/Application Support/Coursier/bin/scala-cli</string>
<string>run</string>
<string>/Users/toniogela/repo/dotfiles/dotfiles.scala</string>
<string>--</string>
<string>/Users/toniogela/repo/dotfiles</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>WatchPaths</key>
<array>
<string>/Users/toniogela/repo/dotfiles</string>
</array>
</dict>
</plist>
//> using scala 3.4.0
//> using platform native
//> using nativeGc none
//> using nativeMode release-full
//> using toolkit typelevel::latest
//> using dep org.typelevel::cats-time::0.5.1
//> using packaging.output dotfiles-watcher
import cats.effect.*
import cats.syntax.all.*
import org.typelevel.cats.time._
import fs2.io.file.*
import fs2.io.process.*
object Main extends IOApp:
def run(args: List[String]): IO[ExitCode] =
git(args.head)("status","--porcelain")
.map(_.isEmpty).ifM(IO.unit, logic(args.head)).as(ExitCode.Success)
.recoverWith {
case ExitException(code, message) => reportToDesktop(code, message)
case t: Throwable => reportToDesktop(1, t.getMessage)
}
def logic(folder: String): IO[Unit] = for {
prevProfile <- ghProfile("show").map(_.trim)
_ <- ghProfile("switch", "personal")
_ <- git(folder)("add", ".")
date <- IO.realTimeInstant.map(_.show)
_ <- git(folder)("-c", "commit.gpgsign=false", "commit", "-m", date)
_ <- git(folder)("push")
_ <- ghProfile("switch", prevProfile)
} yield ()
def ghProfile(command: String*) =
run("/opt/homebrew/bin/gh", ("profile" +: command)*)
def git(folder: String)(command: String*) =
run("/usr/bin/git", ("-C" +: folder +: command)*)
def reportToDesktop(exitCode:Int, message: String): IO[ExitCode] =
fs2.Stream.emit(message).through(
Files[IO].writeUtf8(Path("/Users/toniogela/Desktop/dotfiles_watcher_log"))
).compile.drain.as(ExitCode(exitCode))
def run(command: String, args:String*): IO[String] =
ProcessBuilder(command, args.toList).spawn[IO].use(p =>
(
p.exitValue,
p.stdout.through(fs2.text.utf8.decode).compile.string,
p.stderr.through(fs2.text.utf8.decode).compile.string
).parFlatMapN {
case (0, stdout, stdErr) => IO(stdout)
case (exitCode, stdout, stdErr) =>
val errorMessage: String = List(
Option(stdout).filter(_.nonEmpty).map(s => s"[STDOUT]: $s"),
Option(stdErr).filter(_.nonEmpty).map(s => s"[STDERR]: $s")
).foldLeft(
s"Non zero exit code ($exitCode) for `$command ${args.mkString(" ")}`"
) {
case (summary, Some(err)) => s"$summary\n$err"
case (summary, None) => summary
}
IO.raiseError(new ExitException(exitCode, errorMessage))
}
)
case class ExitException(exitCode:Int, message:String) extends Throwable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment