Skip to content

Commit

Permalink
Merge pull request #26 from tindzk/feat/unicode-logs
Browse files Browse the repository at this point in the history
Improve CLI output
  • Loading branch information
tindzk authored Jul 18, 2019
2 parents a773a9b + 00c8dd8 commit f90b889
Show file tree
Hide file tree
Showing 37 changed files with 458 additions and 287 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ This compiles the module to `build/` and runs it.
* Default paths were chosen not to conflict in any way
* Can be used in CIs like [Drone](https://drone.io/) using pre-built Docker image
* UX
* Colour support
* Readable console output
* True colour output
* User-friendly messages
* Unicode characters
* Project creation wizard
* Packaging support
* Copy over dependencies
Expand Down Expand Up @@ -646,6 +646,21 @@ cachePath = "/home/user/.cache/coursier/v1"

The default values are indicated.

### CLI settings
In the `cli` section, you can find output-related configuration settings:

```toml
[cli]
# Log level
# Possible values: debug, detail, warn, info, error, silent
level = "debug"

# Use Unicode characters to indicate log levels
unicode = true
```

The default values are indicated.

## Git
### .gitignore
For a Seed project, `.gitignore` only needs to contain these three directories:
Expand Down
66 changes: 39 additions & 27 deletions src/main/scala/seed/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,23 +212,22 @@ ${underlined("Usage:")} seed [--build=<path>] [--config=<path>] <command>

def main(args: Array[String]): Unit =
if (args.isEmpty) {
Log.error("Invalid command.")
Log.error("")
val log = Log(SeedConfig.load(None))

Log.error("Create new Seed project file:")
Log.error(Ansi.foreground(ColourScheme.green2)("$ seed init"))
Log.error("")
log.error("No command provided.")
log.newLine()

Log.error("Generate new Bloop configuration:")
Log.error(Ansi.foreground(ColourScheme.green2)("$ seed bloop"))
Log.error("")
log.info( "Create new Seed project file:")
log.debug(Ansi.foreground(ColourScheme.green2)("seed init"))

Log.error("Generate new IDEA configuration:")
Log.error(Ansi.foreground(ColourScheme.green2)("$ seed idea"))
Log.error("")
log.info("Generate new Bloop configuration:")
log.debug(Ansi.foreground(ColourScheme.green2)("seed bloop"))

Log.error("List all available commands:")
Log.error(Ansi.foreground(ColourScheme.green2)("$ seed help"))
log.info("Generate new IDEA configuration:")
log.debug(Ansi.foreground(ColourScheme.green2)("seed idea"))

log.info("List all available commands:")
log.debug(Ansi.foreground(ColourScheme.green2)("seed help"))

sys.exit(1)
} else {
Expand All @@ -237,40 +236,53 @@ ${underlined("Usage:")} seed [--build=<path>] [--config=<path>] <command>
help()
sys.exit(0)
case Success(Config(_, _, Command.Version)) =>
Log.info(Ansi.bold("Seed v" + BuildInfo.Version +
val log = Log(SeedConfig.load(None))
log.info(Ansi.bold("Seed v" + BuildInfo.Version +
" for Bloop v" + BuildInfo.Bloop + "+ " +
"and Coursier v" + BuildInfo.Coursier))
case Success(Config(_, buildPath, Command.Init)) =>
cli.Scaffold.ui(buildPath)
case Success(Config(_, buildPath, Command.Update(preRelease))) =>
cli.Update.ui(buildPath, !preRelease)
case Success(Config(configPath, buildPath, Command.Init)) =>
val config = SeedConfig.load(configPath)
val log = Log(config)
new cli.Scaffold(log).ui(buildPath)
case Success(Config(configPath, buildPath, Command.Update(preRelease))) =>
val config = SeedConfig.load(configPath)
val log = Log(config)
cli.Update.ui(buildPath, !preRelease, log)
case Success(Config(configPath, buildPath, command: Command.Package)) =>
import command._
val config = SeedConfig.load(configPath)
val log = Log(config)
val BuildConfig.Result(build, projectPath, _) =
BuildConfig.load(buildPath, Log).getOrElse(sys.exit(1))
BuildConfig.load(buildPath, log).getOrElse(sys.exit(1))
cli.Package.ui(config, projectPath, build, module, output, libs,
packageConfig)
case Success(Config(configPath, buildPath, command: Command.Generate)) =>
val config = SeedConfig.load(configPath)
val log = Log(config)
val BuildConfig.Result(build, projectPath, _) =
BuildConfig.load(buildPath, Log).getOrElse(sys.exit(1))
cli.Generate.ui(config, projectPath, build, command)
BuildConfig.load(buildPath, log).getOrElse(sys.exit(1))
cli.Generate.ui(config, projectPath, build, command, log)
case Success(Config(configPath, _, command: Command.Server)) =>
val config = SeedConfig.load(configPath)
cli.Server.ui(config, command)
val log = Log(config)
cli.Server.ui(config, command, log)
case Success(Config(configPath, buildPath, command: Command.Build)) =>
val config = SeedConfig.load(configPath)
cli.Build.ui(buildPath, config, command)
val log = Log(config)
cli.Build.ui(buildPath, config, command, log)
case Success(Config(configPath, buildPath, command: Command.Link)) =>
val config = SeedConfig.load(configPath)
cli.Link.ui(buildPath, config, command)
case Success(Config(_, _, command: Command.BuildEvents)) =>
cli.BuildEvents.ui(command)
val log = Log(config)
cli.Link.ui(buildPath, config, command, log)
case Success(Config(configPath, _, command: Command.BuildEvents)) =>
val config = SeedConfig.load(configPath)
val log = Log(config)
cli.BuildEvents.ui(command, log)
case Failure(e) =>
help()
println()
Log.error(e.getMessage)
val log = Log(SeedConfig.load(None))
log.error(e.getMessage)
sys.exit(1)
}
}
Expand Down
76 changes: 66 additions & 10 deletions src/main/scala/seed/Log.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,76 @@ package seed
import seed.cli.util.Ansi._
import seed.cli.util.ColourScheme._

class Log(f: String => Unit, map: String => String = identity) {
def prefix(text: String): Log = new Log(f, text + _)
class Log(f: String => Unit,
map: String => String,
val level: LogLevel,
val unicode: Boolean
) {
import Log._
import LogLevel._

def error(message: String): Unit =
f(foreground(red2)(bold("[error]") + " " + map(message)))

def warn(message: String): Unit =
f(foreground(yellow2)(bold("[warn]") + " " + map(message)))
def prefix(text: String): Log = new Log(f, text + _, level, unicode)

def debug(message: String): Unit =
f(foreground(green2)(bold("[debug]") + " " + map(message)))
if (level <= Debug)
f(foreground(green2)(
bold(if (unicode) "" else "[debug] ") + map(message)))

def detail(message: String): Unit =
if (level <= Detail)
f((" " * (if (unicode) UnicodeLength else NonUnicodeLength)) +
foreground(blue3)(map(message)))

def info(message: String): Unit =
f(foreground(blue2)(bold("[info]") + " " + map(message)))
if (level <= Info)
f(foreground(blue2)(
bold(if (unicode) "" else " [info] ") + map(message)))

def warn(message: String): Unit =
if (level <= Warn)
f(foreground(yellow2)(
bold(if (unicode) "" else " [warn] ") + map(message)))

def error(message: String): Unit =
if (level <= Error)
f(foreground(red2)(
bold(if (unicode) "" else "[error] ") + map(message)))

def newLine(): Unit = f(" ")
}

object Log extends Log(println, identity)
sealed abstract class LogLevel(val index: Int) extends Ordered[LogLevel] {
def compare(that: LogLevel): Int = index.compare(that.index)
}

object LogLevel {
case object Debug extends LogLevel(0)
case object Detail extends LogLevel(1)
case object Info extends LogLevel(2)
case object Warn extends LogLevel(3)
case object Error extends LogLevel(4)
case object Silent extends LogLevel(5)

val All = Map(
"debug" -> Debug,
"detail" -> Detail,
"info" -> Info,
"warn" -> Warn,
"error" -> Error,
"silent" -> Silent
)
}

object Log {
val UnicodeLength = 2
val NonUnicodeLength = 8

def apply(seedConfig: seed.model.Config): Log =
new Log(println, identity, seedConfig.cli.level, seedConfig.cli.unicode)

/** For test cases, only use when errors should be silenced */
def silent: Log = new Log(println, identity, LogLevel.Silent, false)

/** For test cases, only report errors */
def urgent: Log = new Log(println, identity, LogLevel.Error, false)
}
17 changes: 9 additions & 8 deletions src/main/scala/seed/artefact/ArtefactResolution.scala
Original file line number Diff line number Diff line change
Expand Up @@ -248,28 +248,29 @@ object ArtefactResolution {
optionalArtefacts: Boolean,
platformDeps: Set[JavaDep],
compilerDeps: List[Set[JavaDep]],
log: Log
) = {
val silent = packageConfig.silent || seedConfig.resolution.silent

import packageConfig._
val resolvedIvyPath = ivyPath.getOrElse(seedConfig.resolution.ivyPath)
val resolvedCachePath = cachePath.getOrElse(seedConfig.resolution.cachePath)

Log.info("Configured resolvers:")
Log.info(" - " + resolvedIvyPath + " (Ivy)")
Log.info(" - " + resolvedCachePath + " (Coursier)")
build.resolvers.ivy.foreach(ivy => Log.info(" - " + Ansi.italic(ivy.url) + " (Ivy)"))
build.resolvers.maven.foreach(maven => Log.info(" - " + Ansi.italic(maven) + " (Maven)"))
log.info("Configured resolvers:")
log.detail("- " + Ansi.italic(resolvedIvyPath.toString) + " (Ivy)")
log.detail("- " + Ansi.italic(resolvedCachePath.toString) + " (Coursier)")
build.resolvers.ivy.foreach(ivy => log.detail("- " + Ansi.italic(ivy.url) + " (Ivy)"))
build.resolvers.maven.foreach(maven => log.detail("- " + Ansi.italic(maven) + " (Maven)"))

def resolve(deps: Set[JavaDep]) =
Coursier.resolveAndDownload(deps, build.resolvers, resolvedIvyPath,
resolvedCachePath, optionalArtefacts, silent)
resolvedCachePath, optionalArtefacts, silent, log)

Log.info("Resolving platform artefacts...")
log.info("Resolving platform artefacts...")

val platformResolution = resolve(platformDeps)

Log.info("Resolving compiler artefacts...")
log.info("Resolving compiler artefacts...")

// Resolve Scala compilers separately because Coursier merges dependencies
// with different versions
Expand Down
22 changes: 12 additions & 10 deletions src/main/scala/seed/artefact/Coursier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ object Coursier {
def coursierDependencies(deps: Set[JavaDep]): Seq[coursier.core.Dependency] =
deps.map(r => Dependency(Module(Organization(r.organisation), ModuleName(r.artefact)), r.version)).toList

def resolve(all: Set[JavaDep], resolvers: Resolvers, ivyPath: Path, cachePath: Path, silent: Boolean): Resolution =
def resolve(all: Set[JavaDep], resolvers: Resolvers, ivyPath: Path, cachePath: Path, silent: Boolean, log: Log): Resolution =
if (all.isEmpty) Resolution.empty
else {
val organisations = all.map(_.organisation).toList.sorted.map(Ansi.italic).mkString(", ")
Log.debug(s"Resolving ${Ansi.bold(all.size.toString)} dependencies from $organisations...")
log.debug(s"Resolving ${Ansi.bold(all.size.toString)} dependencies from $organisations...")

val mapped = coursierDependencies(all)

val ivy = resolvers.ivy.map { resolver =>
val pattern = resolver.pattern.fold(coursier.ivy.Pattern.default)(p =>
IvyRepository.parse(p) match {
case Left(error) =>
Log.error(s"Could not parse Ivy pattern ${Ansi.italic(p)}: $error")
log.error(s"Could not parse Ivy pattern ${Ansi.italic(p)}: $error")
sys.exit(1)
case Right(parsed) => parsed.pattern
})
Expand All @@ -83,9 +83,9 @@ object Coursier {

val errors = resolution.errors
if (errors.nonEmpty) {
Log.error("Some dependencies could not be resolved:")
log.error("Some dependencies could not be resolved:")
errors.foreach { case ((module, _), _) =>
Log.error(s" - ${module.name} in ${module.organization}")
log.error(s" - ${module.name} in ${module.organization}")
}
sys.exit(1)
}
Expand All @@ -107,7 +107,8 @@ object Coursier {

def localArtefacts(artefacts: Seq[Artefact],
cache: Path,
silent: Boolean
silent: Boolean,
log: Log
): Map[ArtefactUrl, File] = {
val localArtefacts = withLogger(silent) { l =>
val fileCache = FileCache[Task]()
Expand All @@ -122,7 +123,7 @@ object Coursier {
}

if (localArtefacts.exists(_._2.isLeft))
Log.error("Failed to download: " + localArtefacts.filter(_._2.isLeft))
log.error("Failed to download: " + localArtefacts.filter(_._2.isLeft))

localArtefacts
.toMap
Expand All @@ -137,14 +138,15 @@ object Coursier {
ivyPath: Path,
cachePath: Path,
optionalArtefacts: Boolean,
silent: Boolean): ResolutionResult = {
val resolution = resolve(deps, resolvers, ivyPath, cachePath, silent)
silent: Boolean,
log: Log): ResolutionResult = {
val resolution = resolve(deps, resolvers, ivyPath, cachePath, silent, log)
val artefacts = resolution.dependencyArtifacts(
Some(overrideClassifiers(
sources = optionalArtefacts,
javaDoc = optionalArtefacts))).map(_._3).toList

ResolutionResult(resolution, localArtefacts(artefacts, cachePath, silent))
ResolutionResult(resolution, localArtefacts(artefacts, cachePath, silent, log))
}

def resolveSubset(resolution: Resolution,
Expand Down
Loading

0 comments on commit f90b889

Please sign in to comment.