Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweak many things #548

Merged
merged 14 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -84,33 +84,18 @@ object core extends Module {
annotations.jvm(),
util.jvm()
)

object test extends Tests with TestCrossSources {
def ivyDeps = Agg(Deps.utest)
def testFramework = "utest.runner.Framework"
}
}
trait CoreJs extends Core with CaseAppScalaJsModule with MimaChecks {
def moduleDeps = Seq(
annotations.js(),
util.js()
)

object test extends SbtModuleTests with ScalaJSTests with TestCrossSources {
def ivyDeps = Agg(Deps.utest)
def testFramework = "utest.runner.Framework"
}
}
trait CoreNative extends Core with CaseAppScalaNativeModule {
def moduleDeps = Seq(
annotations.native(),
util.native()
)

object test extends SbtModuleTests with ScalaNativeTests with TestCrossSources {
def ivyDeps = Agg(Deps.utest)
def testFramework = "utest.runner.Framework"
}
}

trait Core extends CrossSbtModule with CrossSources with CaseAppPublishModule {
Expand Down Expand Up @@ -171,7 +156,7 @@ object cats2 extends Module {

trait Cats2Jvm extends Cats2 with MimaChecks {
def moduleDeps = Seq(core.jvm())
def sources = cats.jvm().sources()
def sources = T.sources(cats.jvm().sources())

object test extends Tests with TestCrossSources {
def ivyDeps = Agg(Deps.utest)
Expand All @@ -180,7 +165,7 @@ object cats2 extends Module {
}
trait Cats2Js extends Cats2 with CaseAppScalaJsModule with MimaChecks {
def moduleDeps = Seq(core.js())
def sources = cats.js().sources()
def sources = T.sources(cats.js().sources())

object test extends SbtModuleTests with ScalaJSTests with TestCrossSources {
def ivyDeps = Agg(Deps.utest)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package caseapp.cats
package caseapp.catseffect

import caseapp.core.Error
import caseapp.core.help.{Help, WithHelp}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package caseapp.cats
package caseapp

import caseapp.core.argparser.{AccumulatorArgParser, ArgParser}
import cats.data.NonEmptyList

object CatsArgParser {
package object catseffect {
implicit def nonEmptyListArgParser[T](
implicit parser: ArgParser[T]
): AccumulatorArgParser[NonEmptyList[T]] =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package caseapp.cats
package caseapp.catseffect

import _root_.cats.effect._
import _root_.cats.effect.unsafe.implicits.global
import _root_.cats.data.NonEmptyList
import cats.effect._
import cats.effect.unsafe.implicits.global
import cats.data.NonEmptyList
import caseapp._
import caseapp.core.help.Help
import caseapp.core.Error
import utest._

import caseapp.cats.CatsArgParser._

sealed trait RecordedApp {

val stdoutBuff: Ref[IO, List[String]] = Ref.unsafe(List.empty)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package caseapp
package cats
package catseffect

import _root_.cats.data.NonEmptyList
import cats.data.NonEmptyList

object Definitions {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package caseapp.cats
package caseapp.catseffect

import _root_.cats.effect._
import _root_.cats.effect.concurrent.Ref
import _root_.cats.implicits._
import _root_.cats.data.NonEmptyList
import cats.effect._
import cats.effect.concurrent.Ref
import cats.implicits._
import cats.data.NonEmptyList
import caseapp._
import caseapp.core.help.Help
import caseapp.core.Error
import utest._

import caseapp.cats.CatsArgParser._

sealed trait RecordedApp {

val stdoutBuff: Ref[IO, List[String]] = Ref.unsafe(List.empty)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package caseapp
package cats
package catseffect

import _root_.cats.data.NonEmptyList
import cats.data.NonEmptyList

object Definitions {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package caseapp.core.app

import caseapp.core.complete.{Bash, Fish, Zsh}
import caseapp.core.complete.{
Bash,
CompletionsInstallOptions,
CompletionsUninstallOptions,
Fish,
Zsh
}

import java.io.File
import java.nio.charset.{Charset, StandardCharsets}
Expand All @@ -21,7 +27,7 @@ trait PlatformCommandsMethods { self: CommandsEntryPoint =>

// Adapted from https://github.com/VirtusLab/scala-cli/blob/eced0b35c769eca58ae6f1b1a3be0f29a8700859/modules/cli/src/main/scala/scala/cli/commands/installcompletions/InstallCompletions.scala
def completionsInstall(completionsWorkingDirectory: String, args: Seq[String]): Unit = {
val (options, rem) = CaseApp.process[PlatformCommandsMethods.CompletionsInstallOptions](args)
val (options, rem) = CaseApp.process[CompletionsInstallOptions](args)

lazy val completionsDir = Paths.get(options.output.getOrElse(completionsWorkingDirectory))

Expand Down Expand Up @@ -128,7 +134,7 @@ trait PlatformCommandsMethods { self: CommandsEntryPoint =>
}

def completionsUninstall(completionsWorkingDirectory: String, args: Seq[String]): Unit = {
val (options, rem) = CaseApp.process[PlatformCommandsMethods.CompletionsUninstallOptions](args)
val (options, rem) = CaseApp.process[CompletionsUninstallOptions](args)

val name = options.name.getOrElse(Paths.get(progName).getFileName.toString)

Expand Down Expand Up @@ -164,55 +170,6 @@ trait PlatformCommandsMethods { self: CommandsEntryPoint =>
}

object PlatformCommandsMethods {
import caseapp.{HelpMessage, Name}
import caseapp.core.help.Help
import caseapp.core.parser.Parser

// from https://github.com/VirtusLab/scala-cli/blob/eced0b35c769eca58ae6f1b1a3be0f29a8700859/modules/cli/src/main/scala/scala/cli/commands/installcompletions/InstallCompletionsOptions.scala
// format: off
final case class CompletionsInstallOptions(
@HelpMessage("Print completions to stdout")
env: Boolean = false,
@HelpMessage("Custom completions name")
name: Option[String] = None,
@HelpMessage("Name of the shell, either zsh, fish or bash")
@Name("shell")
format: Option[String] = None,
@HelpMessage("Completions output directory (defaults to $XDG_CONFIG_HOME/fish/completions on fish)")
@Name("o")
output: Option[String] = None,
@HelpMessage("Custom banner in comment placed in rc file (bash or zsh only)")
banner: String = "{NAME} completions",
@HelpMessage("Path to `*rc` file, defaults to `.bashrc` or `.zshrc` depending on shell (bash or zsh only)")
rcFile: Option[String] = None
)
// format: on

object CompletionsInstallOptions {
implicit lazy val parser: Parser[CompletionsInstallOptions] = Parser.derive
implicit lazy val help: Help[CompletionsInstallOptions] = Help.derive
}

// from https://github.com/VirtusLab/scala-cli/blob/eced0b35c769eca58ae6f1b1a3be0f29a8700859/modules/cli/src/main/scala/scala/cli/commands/uninstallcompletions/SharedUninstallCompletionsOptions.scala
// format: off
final case class CompletionsUninstallOptions(
@HelpMessage("Path to `*rc` file, defaults to `.bashrc` or `.zshrc` depending on shell (bash or zsh only)")
rcFile: Option[String] = None,
@HelpMessage("Custom banner in comment placed in rc file")
banner: String = "{NAME} completions",
@HelpMessage("Custom completions name")
name: Option[String] = None,
@HelpMessage("Completions output directory (defaults to $XDG_CONFIG_HOME/fish/completions on fish)")
@Name("o")
output: Option[String] = None,
)
// format: on

object CompletionsUninstallOptions {
implicit lazy val parser: Parser[CompletionsUninstallOptions] = Parser.derive
implicit lazy val help: Help[CompletionsUninstallOptions] = Help.derive
}

def getFormat(format: Option[String]): Option[String] =
format.map(_.trim).filter(_.nonEmpty)
.orElse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ abstract class HelpCompanion {
helpMessage: AnnotationOption[HelpMessage, T]
): Help[T] = {

val appName0 = appName().fold(typeable.describe.stripSuffix("Options"))(_.appName)
val appName0 = appName() match {
case None =>
if (typeable.describe == "Options") typeable.describe
else typeable.describe.stripSuffix("Options")
case Some(name) =>
name.appName
}

Help(
parser.args,
Expand Down
1 change: 1 addition & 0 deletions core/shared/src/main/scala/caseapp/core/Arg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import dataclass._
def withDefaultOrigin(defaultOrigin: String): Arg =
if (origin.isEmpty) this.withOrigin(Some(defaultOrigin))
else this
lazy val names = name +: extraNames
}

object Arg {
Expand Down
9 changes: 6 additions & 3 deletions core/shared/src/main/scala/caseapp/core/RemainingArgs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ import dataclass.data
indexedUnparsed: Seq[Indexed[String]]
) {

def remaining: Seq[String] = indexedRemaining.map(_.value)
def unparsed: Seq[String] = indexedUnparsed.map(_.value)
lazy val remaining: Seq[String] = indexedRemaining.map(_.value)
lazy val unparsed: Seq[String] = indexedUnparsed.map(_.value)

/** Arguments both before and after a `--`.
*
* The first `--`, if any, is not included in this list.
*/
def all: Seq[String] =
lazy val all: Seq[String] =
remaining ++ unparsed

lazy val indexed: Seq[Indexed[String]] =
indexedRemaining ++ indexedUnparsed
}
17 changes: 13 additions & 4 deletions core/shared/src/main/scala/caseapp/core/app/CaseApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import caseapp.core.util.Formatter

abstract class CaseApp[T](implicit val parser0: Parser[T], val messages: Help[T]) {

def name: String =
help.progName

def hasHelp: Boolean = true
def hasFullHelp: Boolean = false

def help: Help[T] = messages

def parser: Parser[T] = {
val p = parser0.nameFormatter(nameFormatter)
if (ignoreUnrecognized)
Expand Down Expand Up @@ -69,10 +74,14 @@ abstract class CaseApp[T](implicit val parser0: Parser[T], val messages: Help[T]
exit(1)
}

lazy val finalHelp: Help[_] =
if (hasFullHelp) messages.withFullHelp
else if (hasHelp) messages.withHelp
else messages
lazy val finalHelp: Help[_] = {
val h =
if (hasFullHelp) messages.withFullHelp
else if (hasHelp) messages.withHelp
else messages
if (name == h.progName) h
else h.withProgName(name)
}

def fullHelpAsked(progName: String): Nothing = {
val help = if (progName.isEmpty) finalHelp else finalHelp.withProgName(progName)
Expand Down
2 changes: 0 additions & 2 deletions core/shared/src/main/scala/caseapp/core/app/Command.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ abstract class Command[T](implicit parser: Parser[T], help: Help[T])
extends CaseApp()(parser, help) {
def names: List[List[String]] =
List(List(name))
def name: String =
help.progName
def group: String = ""
def hidden: Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,28 @@ abstract class CommandsEntryPoint extends PlatformCommandsMethods {
printLine("", toStderr = true)
}

def completePrintInstructions(toStderr: Boolean): Unit = {
val formats = Seq(Bash.id, Zsh.id, Fish.id)
printLine("To manually get completions, run", toStderr = toStderr)
printLine("", toStderr = toStderr)
printLine(
s" $progName ${completeCommandName.mkString(" ")} ${formats.mkString("|")} index command...",
toStderr = toStderr
)
printLine("", toStderr = toStderr)
printLine(
"where index starts from one, and command... includes the command name, like",
toStderr = toStderr
)
printLine("", toStderr = toStderr)
printLine(
s" $progName ${completeCommandName.mkString(" ")} ${Zsh.id} 2 $progName --",
toStderr = toStderr
)
printLine("", toStderr = toStderr)
printLine("to get completions for '--'", toStderr = toStderr)
}

def completionsPrintUsage(): Nothing = {
completionsPrintInstructions()
exit(1)
Expand All @@ -58,11 +80,6 @@ abstract class CommandsEntryPoint extends PlatformCommandsMethods {
exit(1)
}

def completePrintUsage(): Nothing = {
completionsPrintInstructions()
exit(1)
}

def completionsWorkingDirectory: Option[String] = None

def completionsMain(args: Array[String]): Unit = {
Expand Down Expand Up @@ -122,8 +139,11 @@ abstract class CommandsEntryPoint extends PlatformCommandsMethods {
case _ =>
completeUnrecognizedFormat(format)
}
case Array("--help" | "--usage" | "-h") =>
completePrintInstructions(toStderr = false)
case _ =>
completePrintUsage()
completePrintInstructions(toStderr = true)
exit(1)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ object ArgParser extends PlatformArgParsers {
/** Look for an implicit `ArgParser[T]` */
def apply[T](implicit parser: ArgParser[T]): ArgParser[T] = parser

implicit def byte: ArgParser[Byte] =
SimpleArgParser.byte

implicit def short: ArgParser[Short] =
SimpleArgParser.short

implicit def int: ArgParser[Int] =
SimpleArgParser.int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ object SimpleArgParser {
def from[T](description: String)(parse: String => Either[Error, T]): SimpleArgParser[T] =
SimpleArgParser(description, (value, _, _) => parse(value))

val byte: SimpleArgParser[Byte] =
from("byte") { s =>
try Right(s.toByte)
catch {
case _: NumberFormatException =>
Left(Error.MalformedValue("byte-sized integer", s))
}
}

val short: SimpleArgParser[Short] =
from("short") { s =>
try Right(s.toShort)
catch {
case _: NumberFormatException =>
Left(Error.MalformedValue("short integer", s))
}
}

val int: SimpleArgParser[Int] =
from("int") { s =>
try Right(s.toInt)
Expand Down
Loading