All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ainargs_native0.4_2.12.0.2.3.source-code.Renderer.scala Maven / Gradle / Ivy

There is a newer version: 0.6.3
Show newest version
package mainargs

import java.io.{PrintWriter, StringWriter}

object Renderer {

  def getLeftColWidth(items: Seq[ArgSig.Terminal[_, _]]) = {
    if (items.isEmpty) 0
    else items.map(renderArgShort(_).length).max
  }

  val newLine = System.lineSeparator()
  def normalizeNewlines(s: String) = s.replace("\r", "").replace("\n", newLine)
  def renderArgShort(arg: ArgSig.Terminal[_, _]) = arg match{
    case arg: ArgSig.Flag[_] =>
      val shortPrefix = arg.shortName.map(c => s"-$c")
      val nameSuffix = arg.name.map(s => s"--$s")
      (shortPrefix ++ nameSuffix).mkString(" ")
    case arg: ArgSig.Simple[_, _] =>
      val shortPrefix = arg.shortName.map(c => s"-$c")
      val typeSuffix = s"<${arg.typeString}>"

      val nameSuffix = if (arg.positional) arg.name else arg.name.map(s => s"--$s")
      (shortPrefix ++ nameSuffix ++ Seq(typeSuffix)).mkString(" ")
    case arg: ArgSig.Leftover[_, _] =>
      s"${arg.name0} <${arg.reader.shortName}>..."
  }


  def renderArg(arg: ArgSig.Terminal[_, _],
                leftOffset: Int,
                wrappedWidth: Int): (String, String) = {
    val wrapped = softWrap(arg.doc.getOrElse(""), leftOffset, wrappedWidth - leftOffset)
    (renderArgShort(arg), wrapped)
  }

  def formatMainMethods(mainMethods: Seq[MainData[_, _]],
                        totalWidth: Int,
                        docsOnNewLine: Boolean,
                        customNames: Map[String, String],
                        customDocs: Map[String, String]) = {
    val flattenedAll: Seq[ArgSig.Terminal[_, _]] =
      mainMethods.map(_.argSigs)
        .flatten
    val leftColWidth = getLeftColWidth(flattenedAll)
    mainMethods match{
      case Seq() => ""
      case Seq(main) =>
        Renderer.formatMainMethodSignature(
          main,
          0,
          totalWidth,
          leftColWidth,
          docsOnNewLine,
          customNames.get(main.name),
          customDocs.get(main.name)
        )
      case _ =>
        val methods =
          for(main <- mainMethods)
            yield formatMainMethodSignature(
              main, 2, totalWidth, leftColWidth, docsOnNewLine,
              customNames.get(main.name), customDocs.get(main.name)
            )

        normalizeNewlines(
          s"""Available subcommands:
             |
             |${methods.mkString(newLine)}""".stripMargin
        )
    }
  }

  def formatMainMethodSignature(main: MainData[_, _],
                                leftIndent: Int,
                                totalWidth: Int,
                                leftColWidth: Int,
                                docsOnNewLine: Boolean,
                                customName: Option[String],
                                customDoc: Option[String]) = {

    val argLeftCol = if (docsOnNewLine) leftIndent + 8 else leftColWidth + leftIndent + 2 + 2
    val args =
      main.argSigs.map(renderArg(_, argLeftCol, totalWidth))

    val leftIndentStr = " " * leftIndent

    def formatArg(lhs: String, rhs: String) = {
      val lhsPadded = lhs.padTo(leftColWidth, ' ')
      val rhsPadded = rhs.linesIterator.mkString(newLine)
      if (rhs.isEmpty) s"$leftIndentStr  $lhs"
      else if (docsOnNewLine){
        s"$leftIndentStr  $lhs\n$leftIndentStr        $rhsPadded"
      }else {
        s"$leftIndentStr  $lhsPadded  $rhsPadded"
      }
    }
    val argStrings = for ((lhs, rhs) <- args) yield formatArg(lhs, rhs)

    val mainDocSuffix = customDoc.orElse(main.doc) match{
      case Some(d) => newLine + leftIndentStr + softWrap(d, leftIndent, totalWidth)
      case None => ""
    }
    s"""$leftIndentStr${customName.getOrElse(main.name)}$mainDocSuffix
       |${argStrings.map(_ + newLine).mkString}""".stripMargin
  }

  def softWrap(s: String, leftOffset: Int, maxWidth: Int) = {
    if (s.isEmpty) s
    else {
      val oneLine = s.linesIterator.mkString(" ").split(' ').filter(_.nonEmpty)

      lazy val indent = " " * leftOffset

      val output = new StringBuilder(oneLine.head)
      var currentLineWidth = oneLine.head.length
      for (chunk <- oneLine.tail) {
        val addedWidth = currentLineWidth + chunk.length + 1
        if (addedWidth > maxWidth) {
          output.append(newLine + indent)
          output.append(chunk)
          currentLineWidth = chunk.length
        } else {
          currentLineWidth = addedWidth
          output.append(' ')
          output.append(chunk)
        }
      }
      output.mkString
    }
  }

  def pluralize(s: String, n: Int) = if (n == 1) s else s + "s"
  def renderEarlyError(result: Result.Failure.Early) = result match {
    case Result.Failure.Early.NoMainMethodsDetected() =>
      "No @main methods declared"
    case Result.Failure.Early.SubcommandNotSpecified(options) =>
      "Need to specify a sub command: " + options.mkString(", ")
    case Result.Failure.Early.UnableToFindSubcommand(options, token) =>
      s"Unable to find subcommand: $token, available subcommands: ${options.mkString(", ")}"
    case Result.Failure.Early.SubcommandSelectionDashes(token) =>
      "To select a subcommand to run, you don't need --s." + Renderer.newLine +
        s"Did you mean `${token.drop(2)}` instead of `$token`?"
  }
  def renderResult(main: MainData[_, _],
                   result: Result.Failure,
                   totalWidth: Int,
                   printHelpOnError: Boolean,
                   docsOnNewLine: Boolean,
                   customName: Option[String],
                   customDoc: Option[String]): String = {

    def expectedMsg() = {
      if (printHelpOnError) {
        val leftColWidth = getLeftColWidth(main.argSigs)
        "Expected Signature: " +
          Renderer.formatMainMethodSignature(
            main,
            0,
            totalWidth,
            leftColWidth,
            docsOnNewLine,
            customName,
            customDoc
          )
      }
      else ""
    }
    result match{
      case err: Result.Failure.Early => renderEarlyError(err)
      case Result.Failure.Exception(t) =>
        val s = new StringWriter()
        val ps = new PrintWriter(s)
        t.printStackTrace(ps)
        ps.close()
        s.toString
      case Result.Failure.MismatchedArguments(missing, unknown, duplicate, incomplete) =>
        val missingStr =
          if (missing.isEmpty) ""
          else {
            val chunks = missing.map(renderArgShort(_))

            val argumentsStr = pluralize("argument", chunks.length)
            s"Missing $argumentsStr: ${chunks.mkString(" ")}" + Renderer.newLine
          }


        val unknownStr =
          if (unknown.isEmpty) ""
          else {
            val argumentsStr = pluralize("argument", unknown.length)
            s"Unknown $argumentsStr: " + unknown.map(Util.literalize(_)).mkString(" ") + Renderer.newLine
          }

        val duplicateStr =
          if (duplicate.isEmpty) ""
          else {
            val lines =
              for ((sig, options) <- duplicate)
                yield {
                  s"Duplicate arguments for ${renderArgShort(sig)}: " +
                    options.map(Util.literalize(_)).mkString(" ") + Renderer.newLine
                }

            lines.mkString

          }
        val incompleteStr = incomplete match{
          case None => ""
          case Some(sig) =>
            s"Incomplete argument ${renderArgShort(sig)} is missing a corresponding value" +
              Renderer.newLine

        }

        Renderer.normalizeNewlines(
          s"""$missingStr$unknownStr$duplicateStr$incompleteStr${expectedMsg()}
             |""".stripMargin
        )

      case Result.Failure.InvalidArguments(x) =>
        val thingies = x.map{
          case Result.ParamError.Failed(p, vs, errMsg) =>
            val literalV = vs.map(Util.literalize(_)).mkString(" ")
            s"Invalid argument ${renderArgShort(p)} failed to parse $literalV due to $errMsg"
          case Result.ParamError.Exception(p, vs, ex) =>
            val literalV = vs.map(Util.literalize(_)).mkString(" ")
            s"Invalid argument ${renderArgShort(p)} failed to parse $literalV due to $ex"
          case Result.ParamError.DefaultFailed(p, ex) =>
            s"Invalid argument ${renderArgShort(p)}'s default value failed to evaluate with $ex"
        }

        Renderer.normalizeNewlines(
          s"""${thingies.mkString(Renderer.newLine)}
             |${expectedMsg()}
          """.stripMargin
        )
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy