sourcecamNodePlugin::NodePlugin.fan

// History:
//  Jan 30 13 tcolar Creation
//

using camembert
using gfx
using util
using netColarUtils

**
** NodePlugin
**
const class NodePlugin : BasicPlugin
{
  static const Str _name := "Node"
  const NodeDocs docProv := NodeDocs()
  const NodeCommands cmds

  override const Image icon := Image(`fan://camNodePlugin/res/node.png`)
  override const Str name := _name
  override PluginCommands? commands() { cmds}
  override PluginDocs? docProvider() {docProv}
  override Type? envType() {NodeEnv#}

  new make()
  {
    cmds = NodeCommands(this)
  }

  override Bool isProject(File dir)
  {
    if(dir.path.contains("node_modules"))
      return false
    if(isCustomPrj(dir, "Node")) return true
    return dir.isDir && (dir + `package.json`).exists
  }

  override Str prjName(File prjDir)
  {
    Str? name
    json := prjDir + `package.json`
    if(json.exists)
    {
      try
      {
        data := (Str:Obj?) JsonInStream(json.in).readJson
        name = data["name"]
      }
      catch(Err e){e.trace}
    }
    return name ?: prjDir.name  // fallback
  }
}

const class NodeCommands : PluginCommands
{
  override const Cmd run
  override const Cmd runSingle
  override const Cmd test
  override const Cmd testSingle

  new make(NodePlugin plugin)
  {
    node := "{{env_home}}/bin/node"
    npm := "{{env_home}}/bin/npm"

    run         = BasicPluginCmd(plugin, "Run", [node, "app.js"],
                                 ExecCmdInteractive.onetime, nodeFinder)
    runSingle   = BasicPluginCmd(plugin, "RunSingle", [node, "{{cur_file}}"],
                                 ExecCmdInteractive.always, nodeFinder)
    test        = BasicPluginCmd(plugin, "Test", [npm, "test"],
                                 ExecCmdInteractive.onetime, nodeFinder)
    testSingle  = BasicPluginCmd(plugin, "TestSingle", [node, "{{cur_file}}"],
                                 ExecCmdInteractive.always, nodeFinder)
  }

  static const |Str -> Item?| nodeFinder := |Str str -> Item?|
  {
    if(str.size < 4) return null
    p1 := str.index("(", 4); if (p1 == null) return null
    c  := str.index(":", p1 + 1); if (c == null) return null
    p2 := str.index(")", p1); if (p2 == null) return null
    if(p1 > c || c > p2) return null
    file := File.os(str[p1+1 ..< c].trim)
    if(! file.exists) return null
    pos := str[c+1 ..< p2].split(':')
    if(pos.size != 2) return null
    line := pos[0].toInt(10, false) ?: 1
    col  := pos[1].toInt(10, false) ?: 1
    text := str
    return FileItem.makeFile(file).setDis(text).setLoc(
          ItemLoc{it.line = line-1; it.col  = col-1}).setIcon(
          Sys.cur.theme.iconErr)
  }
}

@Serializable
const class NodeEnv : BasicEnv
{
  @Setting{help = ["Node Home"]}
  const Uri nodeHome := `/usr/share/node/`

  override Uri? envHome() {return nodeHome}

  new make(|This|? f := null) : super(f)
  {
    if (f != null) f(this)
  }
}