diff --git a/src/nimble.nim b/src/nimble.nim index 6ab7fc1..d813b20 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -813,59 +813,63 @@ proc doAction(options: Options) = if not existsDir(options.getPkgsDir): createDir(options.getPkgsDir) - var command = getNimScriptCommand().parseActionType() - # The loop is necessary to support tasks using `setCommand`. - var moreCommands = true - while moreCommands: - moreCommands = false - case command - of actionUpdate: - update(options) - of actionInstall: - let (_, pkgInfo) = install(options.action.packages, options) - if options.action.packages.len == 0: - nimScriptHint(pkgInfo) - of actionUninstall: - uninstall(options) - of actionSearch: - search(options) - of actionList: - if options.queryInstalled: listInstalled(options) - else: list(options) - of actionPath: - listPaths(options) - of actionBuild: - build(options) - of actionCompile: - compile(options) - of actionInit: - init(options) - of actionPublish: - var pkgInfo = getPkgInfo(getCurrentDir()) - publish(pkgInfo) - of actionDump: - dump(options) - of actionTasks: - listTasks(options) - of actionVersion: - writeVersion() - of actionNil: - assert false - of actionCustom: - # Custom command. Attempt to call a NimScript task. - let nimbleFile = findNimbleFile(getCurrentDir(), true) - let oldCmd = getNimScriptCommand() - if not nimbleFile.isNimScript(): - writeHelp() + case options.action.typ + of actionUpdate: + update(options) + of actionInstall: + let (_, pkgInfo) = install(options.action.packages, options) + if options.action.packages.len == 0: + nimScriptHint(pkgInfo) + of actionUninstall: + uninstall(options) + of actionSearch: + search(options) + of actionList: + if options.queryInstalled: listInstalled(options) + else: list(options) + of actionPath: + listPaths(options) + of actionBuild: + build(options) + of actionCompile: + compile(options) + of actionInit: + init(options) + of actionPublish: + var pkgInfo = getPkgInfo(getCurrentDir()) + publish(pkgInfo) + of actionDump: + dump(options) + of actionTasks: + listTasks(options) + of actionVersion: + writeVersion() + of actionNil: + assert false + of actionCustom: + # Custom command. Attempt to call a NimScript task. + let nimbleFile = findNimbleFile(getCurrentDir(), true) + if not nimbleFile.isNimScript(): + writeHelp() - if not execTask(nimbleFile, oldCmd): - echo("FAILURE: Could not find task ", oldCmd, " in ", nimbleFile) - writeHelp() - if getNimScriptCommand().normalize == "nop": - echo("WARNING: Using `setCommand 'nop'` is not necessary.") - break - command = getNimScriptCommand().parseActionType() - moreCommands = hasTaskRequestedCommand() + let execResult = execTask(nimbleFile, options.action.command) + if not execResult.success: + echo("FAILURE: Could not find task ", options.action.command, " in ", + nimbleFile) + writeHelp() + if execResult.command.normalize == "nop": + echo("WARNING: Using `setCommand 'nop'` is not necessary.") + return + if execResult.hasTaskRequestedCommand(): + var newOptions = initOptions() + newOptions.config = options.config + newOptions.nimbleData = options.nimbleData + parseCommand(execResult.command, newOptions) + for arg in execResult.arguments: + parseArgument(arg, newOptions) + for flag, val in execResult.flags: + parseFlag(flag, val, newOptions) + doAction(newOptions) when isMainModule: when defined(release): diff --git a/src/nimblepkg/nimscriptsupport.nim b/src/nimblepkg/nimscriptsupport.nim index 447b9e5..1c78f7d 100644 --- a/src/nimblepkg/nimscriptsupport.nim +++ b/src/nimblepkg/nimscriptsupport.nim @@ -15,7 +15,14 @@ from compiler/idents import getIdent from compiler/astalgo import strTableGet import nimbletypes, version -import os, strutils +import os, strutils, strtabs, times, osproc + +type + ExecutionResult* = object + success*: bool + command*: string + arguments*: seq[string] + flags*: StringTableRef const internalCmd = "NimbleInternal" @@ -58,7 +65,112 @@ proc extractRequires(result: var seq[PkgTuple]) = else: raiseVariableError("requiresData", "seq[(string, VersionReq)]") -proc execScript(scriptName: string) = +proc setupVM(module: PSym; scriptName: string, + flags: StringTableRef): PEvalContext = + ## This procedure is exported in the compiler sources, but its implementation + ## is too Nim-specific to be used by Nimble. + ## Specifically, the implementation of ``switch`` is problematic. Sooo + ## I simply copied it here and edited it :) + + result = newCtx(module) + result.mode = emRepl + registerAdditionalOps(result) + + # captured vars: + var errorMsg: string + var vthisDir = scriptName.splitFile.dir + + proc listDirs(a: VmArgs, filter: set[PathComponent]) = + let dir = getString(a, 0) + var result: seq[string] = @[] + for kind, path in walkDir(dir): + if kind in filter: result.add path + setResult(a, result) + + template cbconf(name, body) {.dirty.} = + result.registerCallback "stdlib.system." & astToStr(name), + proc (a: VmArgs) = + body + + template cbos(name, body) {.dirty.} = + result.registerCallback "stdlib.system." & astToStr(name), + proc (a: VmArgs) = + try: + body + except OSError: + errorMsg = getCurrentExceptionMsg() + + # Idea: Treat link to file as a file, but ignore link to directory to prevent + # endless recursions out of the box. + cbos listFiles: + listDirs(a, {pcFile, pcLinkToFile}) + cbos listDirs: + listDirs(a, {pcDir}) + cbos removeDir: + os.removeDir getString(a, 0) + cbos removeFile: + os.removeFile getString(a, 0) + cbos createDir: + os.createDir getString(a, 0) + cbos getOsError: + setResult(a, errorMsg) + cbos setCurrentDir: + os.setCurrentDir getString(a, 0) + cbos getCurrentDir: + setResult(a, os.getCurrentDir()) + cbos moveFile: + os.moveFile(getString(a, 0), getString(a, 1)) + cbos copyFile: + os.copyFile(getString(a, 0), getString(a, 1)) + cbos getLastModificationTime: + setResult(a, toSeconds(getLastModificationTime(getString(a, 0)))) + + cbos rawExec: + setResult(a, osproc.execCmd getString(a, 0)) + + cbconf getEnv: + setResult(a, os.getEnv(a.getString 0)) + cbconf existsEnv: + setResult(a, os.existsEnv(a.getString 0)) + cbconf dirExists: + setResult(a, os.dirExists(a.getString 0)) + cbconf fileExists: + setResult(a, os.fileExists(a.getString 0)) + + cbconf thisDir: + setResult(a, vthisDir) + cbconf put: + options.setConfigVar(getString(a, 0), getString(a, 1)) + cbconf get: + setResult(a, options.getConfigVar(a.getString 0)) + cbconf exists: + setResult(a, options.existsConfigVar(a.getString 0)) + cbconf nimcacheDir: + setResult(a, options.getNimcacheDir()) + cbconf paramStr: + setResult(a, os.paramStr(int a.getInt 0)) + cbconf paramCount: + setResult(a, os.paramCount()) + cbconf cmpIgnoreStyle: + setResult(a, strutils.cmpIgnoreStyle(a.getString 0, a.getString 1)) + cbconf cmpIgnoreCase: + setResult(a, strutils.cmpIgnoreCase(a.getString 0, a.getString 1)) + cbconf setCommand: + options.command = a.getString 0 + let arg = a.getString 1 + if arg.len > 0: + gProjectName = arg + try: + gProjectFull = canonicalizePath(gProjectPath / gProjectName) + except OSError: + gProjectFull = gProjectName + cbconf getCommand: + setResult(a, options.command) + cbconf switch: + if not flags.isNil: + flags[a.getString 0] = a.getString 1 + +proc execScript(scriptName: string, flags: StringTableRef) = ## Executes the specified script. ## ## No clean up is performed and must be done manually! @@ -77,13 +189,15 @@ proc execScript(scriptName: string) = var m = makeModule(scriptName) incl(m.flags, sfMainModule) - vm.globalCtx = setupVM(m, scriptName) + vm.globalCtx = setupVM(m, scriptName, flags) compileSystemModule() processModule(m, llStreamOpen(scriptName, fmRead), nil) proc cleanup() = # ensure everything can be called again: + options.gProjectName = "" + options.command = "" resetAllModulesHard() clearPasses() msgs.gErrorMax = 1 @@ -106,7 +220,7 @@ proc readPackageInfoFromNims*(scriptName: string; result: var PackageInfo) = previousMsg = output # Execute the nimscript file. - execScript(scriptName) + execScript(scriptName, nil) # Extract all the necessary fields populated by the nimscript file. template trivialField(field) = @@ -148,38 +262,47 @@ proc readPackageInfoFromNims*(scriptName: string; result: var PackageInfo) = cleanup() -proc execTask*(scriptName, taskName: string): bool = +proc execTask*(scriptName, taskName: string): ExecutionResult = ## Executes the specified task in the specified script. ## ## `scriptName` should be a filename pointing to the nimscript file. - result = true + result.success = true + result.flags = newStringTable() options.command = internalCmd echo("Executing task ", taskName, " in ", scriptName) - execScript(scriptName) + execScript(scriptName, result.flags) # Explicitly execute the task procedure, instead of relying on hack. assert vm.globalCtx.module.kind == skModule let prc = vm.globalCtx.module.tab.strTableGet(getIdent(taskName & "Task")) if prc.isNil: # Procedure not defined in the NimScript module. - return false + result.success = false + return discard vm.globalCtx.execProc(prc, []) + + # Read the command, arguments and flags set by the executed task. + result.command = options.command + result.arguments = @[] + for arg in options.gProjectName.split(): + result.arguments.add(arg) + cleanup() -proc getNimScriptCommand*(): string = +proc getNimScriptCommand(): string = options.command -proc setNimScriptCommand*(command: string) = +proc setNimScriptCommand(command: string) = options.command = command -proc hasTaskRequestedCommand*(): bool = +proc hasTaskRequestedCommand*(execResult: ExecutionResult): bool = ## Determines whether the last executed task used ``setCommand`` - return getNimScriptCommand() != internalCmd + return execResult.command != internalCmd proc listTasks*(scriptName: string) = setNimScriptCommand("help") - execScript(scriptName) + execScript(scriptName, nil) # TODO: Make the 'task' template generate explicit data structure containing # all the task names + descriptions. cleanup() diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index e8b4131..74e843e 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -1,7 +1,7 @@ # Copyright (C) Dominik Picheta. All rights reserved. # BSD License. Look at license.txt for more info. -import json, strutils, os, parseopt +import json, strutils, os, parseopt, strtabs import nimblepkg/config, nimblepkg/version, nimblepkg/nimscriptsupport, nimblepkg/tools @@ -39,7 +39,10 @@ type file*: string backend*: string compileOptions*: seq[string] - else: nil + of actionCustom: + command*: string + arguments*: seq[string] + flags*: StringTableRef ForcePrompt* = enum dontForcePrompt, forcePromptYes, forcePromptNo @@ -144,7 +147,11 @@ proc initAction*(options: var Options, key: string) = options.action.search = @[] of actionUninstall: options.action.packages = @[] - of actionBuild, actionPublish, actionCustom, actionList, actionTasks, + of actionCustom: + options.action.command = key + options.action.arguments = @[] + options.action.flags = newStringTable() + of actionBuild, actionPublish, actionList, actionTasks, actionNil, actionVersion: discard proc prompt*(options: Options, question: string): bool = @@ -192,7 +199,6 @@ proc getBinDir*(options: Options): string = options.config.nimbleDir / "bin" proc parseCommand*(key: string, result: var Options) = - setNimScriptCommand(key) result.action.typ = parseActionType(key) initAction(result, key) @@ -222,6 +228,8 @@ proc parseArgument*(key: string, result: var Options) = result.action.file = key of actionList, actionBuild, actionPublish: writeHelp() + of actionCustom: + result.action.arguments.add(key) else: discard @@ -232,7 +240,10 @@ proc parseFlag*(flag, val: string, result: var Options) = result.action.compileOptions.add("--" & flag) else: result.action.compileOptions.add("--" & flag & ":" & val) + of actionCustom: + result.action.flags[flag] = val else: + # TODO: These should not be global. case flag.normalize() of "help", "h": writeHelp() of "version", "v": @@ -246,9 +257,26 @@ proc parseFlag*(flag, val: string, result: var Options) = else: raise newException(NimbleError, "Unknown option: --" & flag) -proc parseCmdLine*(): Options = +proc initOptions*(): Options = result.action.typ = actionNil + +proc parseMisc(): Options = + result = initOptions() result.config = parseConfig() + + # Load nimbledata.json + let nimbledataFilename = result.getNimbleDir() / "nimbledata.json" + if fileExists(nimbledataFilename): + try: + result.nimbleData = parseFile(nimbledataFilename) + except: + raise newException(NimbleError, "Couldn't parse nimbledata.json file " & + "located at " & nimbledataFilename) + else: + result.nimbleData = %{"reverseDeps": newJObject()} + +proc parseCmdLine*(): Options = + result = parseMisc() for kind, key, val in getOpt(): case kind of cmdArgument: @@ -267,13 +295,3 @@ proc parseCmdLine*(): Options = # Rename deprecated babel dir. renameBabelToNimble(result) - # Load nimbledata.json - let nimbledataFilename = result.getNimbleDir() / "nimbledata.json" - if fileExists(nimbledataFilename): - try: - result.nimbleData = parseFile(nimbledataFilename) - except: - raise newException(NimbleError, "Couldn't parse nimbledata.json file " & - "located at " & nimbledataFilename) - else: - result.nimbleData = %{"reverseDeps": newJObject()} diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index bfc0af2..b118df7 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -470,7 +470,7 @@ proc getDownloadDirName*(pkg: Package, verRange: VersionRange): string = result.add verSimple proc isNimScript*(nf: NimbleFile): bool = - readPackageInfo(nf).isNimScript + result = readPackageInfo(nf).isNimScript when isMainModule: doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == diff --git a/tests/nimscript/nimscript.nimble b/tests/nimscript/nimscript.nimble index 59e6c41..4a71b56 100644 --- a/tests/nimscript/nimscript.nimble +++ b/tests/nimscript/nimscript.nimble @@ -13,3 +13,10 @@ requires "nim >= 0.12.1" task test, "test description": echo(5+5) + +task c_test, "Testing `setCommand \"c\", \"nimscript.nim\"`": + setCommand "c", "nimscript.nim" + +task cr, "Testing `nimble c -r nimscript.nim` via setCommand": + --r + setCommand "c", "nimscript.nim" \ No newline at end of file diff --git a/tests/tester.nim b/tests/tester.nim index 9bb1369..7a77c68 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -29,10 +29,25 @@ test "can execute nimscript tasks": check exitCode == QuitSuccess check lines[^1] == "10" +test "can use nimscript's setCommand": + cd "nimscript": + let (output, exitCode) = execCmdEx("../" & path & " cTest") + let lines = output.strip.splitLines() + check exitCode == QuitSuccess + check "Hint: operation successful".normalize in lines[^1].normalize + +test "can use nimscript's setCommand with flags": + cd "nimscript": + let (output, exitCode) = execCmdEx("../" & path & " cr") + let lines = output.strip.splitLines() + check exitCode == QuitSuccess + check "Hint: operation successful".normalize in lines[^2].normalize + check "Hello World".normalize in lines[^1].normalize + test "can list nimscript tasks": cd "nimscript": let (output, exitCode) = execCmdEx("../" & path & " tasks") - check output.strip == "test test description" + check "test test description".normalize in output.normalize check exitCode == QuitSuccess test "can install packagebin2":