sourcestudsTools::Cmd.fan

//
// Copyright (c) 2016, Andy Frank
// Licensed under the Apache License version 2.0
//
// History:
//   22 Aug 2016  Andy Frank  Creation
//

using concurrent

**
** Cmd models a build tool command.
**
abstract const class Cmd
{
  ** Unique name of this command.
  abstract Str name()

  ** Help signature.
  virtual Str sig() { "" }

  ** One-line short help text for this command.
  abstract Str helpShort()

  ** Full help text for this command, or 'null' for none.
  abstract Str? helpFull()

  ** Run this command and return exit code.
  abstract Int run()

  ** Convenience for 'Env.cur.out.printLine'.
  Void info(Str msg) { Env.cur.out.printLine(msg) }

  ** Convenience for 'Env.cur.err.printLine'.
  Void err(Str msg) { Env.cur.err.printLine(msg) }

  ** Conveniece for 'Env.cur.prompt'.
  Str? prompt(Str msg) { Env.cur.prompt(msg) }

  ** Convenience for 'prompt' with yes/no choice, return true for
  ** 'y' or false for 'n'.  If enter is pressed with no choice,
  ** then use 'def' for default.
  Bool promptYesNo(Str msg, Str def := "y")
  {
    r := prompt(msg)
    if (r == "") r = def
    return r == "y"
  }

  ** Convenience for 'prompt' with an integer range choice, returns
  ** the selected choise, or aborts if invalid input or out of range.
  Int promptChoice(Str msg, Range range)
  {
    s := prompt("$msg [$range] ")
    i := s.toInt(10, false)
    if (i == null || !range.contains(i)) abort("invalid selection '$s'")
    return i
  }

  ** Profile configuration.
  static Str:Str profile() { Actor.locals["cmd.profile"] }

  ** Props configuration.
  static Props props()
  {
    p := Actor.locals["cmd.props"] as Props
    if (p == null) Actor.locals["cmd.props"] = p = Props()
    return p
  }

  ** List all commands.
  static Cmd[] list() { Actor.locals["cmd.list"] }

  ** Get the given command, or 'null' if not found.
  static Cmd? get(Str name) { list.find |c| { c.name == name } }

  ** Get the current working directory.
  File workDir()
  {
    path := Env.cur.vars["user.dir"]
    return path==null ? Env.cur.workDir : File.os(path)
  }

  ** List command arguments. Arguments are any terms that
  ** trail the command name not prefixied with '-'.
  Str[] args() { Actor.locals["cmd.args"] }

  ** List of command options.  Options are any terms that
  ** trail the command name and are prexifed with '-'.
  Str[] opts() { Actor.locals["cmd.opts"] }

  ** Print the given error message, show command help, then exit with error code.
  Void abort(Str msg)
  {
    err(msg)
// TODO?
//    showHelp
    Env.cur.exit(1)
  }

  ** Convenience to display help for this command.
  Void showHelp()
  {
    Cmd.get("help")->showDetails(name)
  }

  ** Prompt to select a current release. Returns selected release fw file.
  @NoDoc File? promptRelease()
  {
    relDir := Env.cur.workDir + `studs/releases/`
    rels   := relDir.listFiles.findAll |f| { f.ext == "fw" }

    // sort by filename
    rels.sort |a,b| { a.name <=> b.name }

    // bail if no releases found
    if (rels.isEmpty) abort("no releases found")

    // prompt for release selection
    File? rel
    if (rels.size == 1) rel = rels.first
    else
    {
      echo("Available releases:")
      w := rels.map |f| { f.name.size }.max
      rels.each |f,i|
      {
        size := f.size.toLocale("B")
        echo(" [${i+1}] " + f.name.padr(w) + "  ($size)")
      }
      sel := promptChoice("Which release do you want to burn?", 1..rels.size)
      rel = rels[sel-1]
    }

    return rel
  }
}