diff options
Diffstat (limited to 'lib/system/nimscript.nim')
-rw-r--r-- | lib/system/nimscript.nim | 349 |
1 files changed, 262 insertions, 87 deletions
diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index aaba11324..cf81f6d86 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -7,40 +7,57 @@ # distribution, for details about the copyright. # +## To learn about scripting in Nim see `NimScript<nims.html>`_ # Nim's configuration system now uses Nim for scripting. This module provides # a few things that are required for this to work. +const + buildOS* {.magic: "BuildOS".}: string = "" + ## The OS this build is running on. Can be different from `system.hostOS` + ## for cross compilations. + + buildCPU* {.magic: "BuildCPU".}: string = "" + ## The CPU this build is running on. Can be different from `system.hostCPU` + ## for cross compilations. + template builtin = discard # We know the effects better than the compiler: {.push hint[XDeclaredButNotUsed]: off.} -proc listDirs*(dir: string): seq[string] = - ## Lists all the subdirectories (non-recursively) in the directory `dir`. - builtin -proc listFiles*(dir: string): seq[string] = - ## Lists all the files (non-recursively) in the directory `dir`. - builtin - -proc removeDir(dir: string){. +proc listDirsImpl(dir: string): seq[string] {. + tags: [ReadIOEffect], raises: [OSError].} = builtin +proc listFilesImpl(dir: string): seq[string] {. + tags: [ReadIOEffect], raises: [OSError].} = builtin +proc removeDir(dir: string, checkDir = true) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc removeFile(dir: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc moveFile(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin +proc moveDir(src, dest: string) {. + tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc copyFile(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin +proc copyDir(src, dest: string) {. + tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = builtin -proc getOsError: string = builtin + +proc getError: string = builtin proc setCurrentDir(dir: string) = builtin -proc getCurrentDir(): string = builtin +proc getCurrentDir*(): string = + ## Retrieves the current working directory. + builtin proc rawExec(cmd: string): int {.tags: [ExecIOEffect], raises: [OSError].} = builtin +proc warningImpl(arg, orig: string) = discard +proc hintImpl(arg, orig: string) = discard + proc paramStr*(i: int): string = - ## Retrieves the ``i``'th command line parameter. + ## Retrieves the `i`'th command line parameter. builtin proc paramCount*(): int = @@ -49,9 +66,34 @@ proc paramCount*(): int = proc switch*(key: string, val="") = ## Sets a Nim compiler command line switch, for - ## example ``switch("checks", "on")``. + ## example `switch("checks", "on")`. builtin +proc warning*(name: string; val: bool) = + ## Disables or enables a specific warning. + let v = if val: "on" else: "off" + warningImpl(name & ":" & v, "warning:" & name & ":" & v) + +proc hint*(name: string; val: bool) = + ## Disables or enables a specific hint. + let v = if val: "on" else: "off" + hintImpl(name & ":" & v, "hint:" & name & ":" & v) + +proc patchFile*(package, filename, replacement: string) = + ## Overrides the location of a given file belonging to the + ## passed package. + ## If the `replacement` is not an absolute path, the path + ## is interpreted to be local to the Nimscript file that contains + ## the call to `patchFile`, Nim's `--path` is not used at all + ## to resolve the filename! + ## The compiler also performs `path substitution <nimc.html#compiler-usage-commandminusline-switches>`_ on `replacement`. + ## + ## Example: + ## ```nim + ## patchFile("stdlib", "asyncdispatch", "patches/replacement") + ## ``` + discard + proc getCommand*(): string = ## Gets the Nim command that the compiler has been invoked with, for example ## "c", "js", "build", "help". @@ -69,12 +111,20 @@ proc cmpic*(a, b: string): int = ## Compares `a` and `b` ignoring case. cmpIgnoreCase(a, b) -proc getEnv*(key: string): string {.tags: [ReadIOEffect].} = +proc getEnv*(key: string; default = ""): string {.tags: [ReadIOEffect].} = ## Retrieves the environment variable of name `key`. builtin proc existsEnv*(key: string): bool {.tags: [ReadIOEffect].} = - ## Checks for the existance of an environment variable named `key`. + ## Checks for the existence of an environment variable named `key`. + builtin + +proc putEnv*(key, val: string) {.tags: [WriteIOEffect].} = + ## Sets the value of the environment variable named `key` to `val`. + builtin + +proc delEnv*(key: string) {.tags: [WriteIOEffect].} = + ## Deletes the environment variable named `key`. builtin proc fileExists*(filename: string): bool {.tags: [ReadIOEffect].} = @@ -86,13 +136,9 @@ proc dirExists*(dir: string): bool {. ## Checks if the directory `dir` exists. builtin -proc existsFile*(filename: string): bool = - ## An alias for ``fileExists``. - fileExists(filename) - -proc existsDir*(dir: string): bool = - ## An alias for ``dirExists``. - dirExists(dir) +proc selfExe*(): string {.deprecated: "Deprecated since v1.7; Use getCurrentCompilerExe".} = + ## Returns the currently running nim or nimble executable. + builtin proc toExe*(filename: string): string = ## On Windows adds ".exe" to `filename`, else returns `filename` unmodified. @@ -104,16 +150,28 @@ proc toDll*(filename: string): string = proc strip(s: string): string = var i = 0 - while s[i] in {' ', '\c', '\L'}: inc i + while s[i] in {' ', '\c', '\n'}: inc i result = s.substr(i) + if result[0] == '"' and result[^1] == '"': + result = result[1..^2] template `--`*(key, val: untyped) = - ## A shortcut for ``switch(astToStr(key), astToStr(val))``. - switch(astToStr(key), strip astToStr(val)) + ## A shortcut for `switch <#switch,string,string>`_ + ## Example: + ## ```nim + ## --path:somePath # same as switch("path", "somePath") + ## --path:"someOtherPath" # same as switch("path", "someOtherPath") + ## --hint:"[Conf]:off" # same as switch("hint", "[Conf]:off") + ## ``` + switch(strip(astToStr(key)), strip(astToStr(val))) template `--`*(key: untyped) = - ## A shortcut for ``switch(astToStr(key)``. - switch(astToStr(key), "") + ## A shortcut for `switch <#switch,string,string>`_ + ## Example: + ## ```nim + ## --listCmd # same as switch("listCmd") + ## ``` + switch(strip(astToStr(key))) type ScriptMode* {.pure.} = enum ## Controls the behaviour of the script. @@ -126,20 +184,33 @@ var mode*: ScriptMode ## Set this to influence how mkDir, rmDir, rmFile etc. ## behave +template checkError(exc: untyped): untyped = + let err = getError() + if err.len > 0: raise newException(exc, err) + template checkOsError = - let err = getOsError() - if err.len > 0: raise newException(OSError, err) + checkError(OSError) template log(msg: string, body: untyped) = - if mode == ScriptMode.Verbose or mode == ScriptMode.Whatif: + if mode in {ScriptMode.Verbose, ScriptMode.Whatif}: echo "[NimScript] ", msg - if mode != ScriptMode.WhatIf: + if mode != ScriptMode.Whatif: body -proc rmDir*(dir: string) {.raises: [OSError].} = +proc listDirs*(dir: string): seq[string] = + ## Lists all the subdirectories (non-recursively) in the directory `dir`. + result = listDirsImpl(dir) + checkOsError() + +proc listFiles*(dir: string): seq[string] = + ## Lists all the files (non-recursively) in the directory `dir`. + result = listFilesImpl(dir) + checkOsError() + +proc rmDir*(dir: string, checkDir = false) {.raises: [OSError].} = ## Removes the directory `dir`. log "rmDir: " & dir: - removeDir dir + removeDir(dir, checkDir = checkDir) checkOsError() proc rmFile*(file: string) {.raises: [OSError].} = @@ -161,24 +232,63 @@ proc mvFile*(`from`, to: string) {.raises: [OSError].} = moveFile `from`, to checkOsError() +proc mvDir*(`from`, to: string) {.raises: [OSError].} = + ## Moves the dir `from` to `to`. + log "mvDir: " & `from` & ", " & to: + moveDir `from`, to + checkOsError() + proc cpFile*(`from`, to: string) {.raises: [OSError].} = ## Copies the file `from` to `to`. log "cpFile: " & `from` & ", " & to: copyFile `from`, to checkOsError() -proc exec*(command: string) = - ## Executes an external process. +proc cpDir*(`from`, to: string) {.raises: [OSError].} = + ## Copies the dir `from` to `to`. + log "cpDir: " & `from` & ", " & to: + copyDir `from`, to + checkOsError() + +proc exec*(command: string) {. + raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} = + ## Executes an external process. If the external process terminates with + ## a non-zero exit code, an OSError exception is raised. The command is + ## executed relative to the current source path. + ## + ## .. note:: If you need a version of `exec` that returns the exit code + ## and text output of the command, you can use `system.gorgeEx + ## <system.html#gorgeEx,string,string,string>`_. log "exec: " & command: if rawExec(command) != 0: raise newException(OSError, "FAILED: " & command) checkOsError() proc exec*(command: string, input: string, cache = "") {. - raises: [OSError], tags: [ExecIOEffect].} = - ## Executes an external process. + raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} = + ## Executes an external process. If the external process terminates with + ## a non-zero exit code, an OSError exception is raised. + ## + ## .. warning:: This version of `exec` is executed relative to the nimscript + ## module path, which affects how the command resolves relative paths. Thus + ## it is generally better to use `gorgeEx` directly when you need more + ## control over the execution environment or when working with commands + ## that deal with relative paths. log "exec: " & command: - echo staticExec(command, input, cache) + let (output, exitCode) = gorgeEx(command, input, cache) + echo output + if exitCode != 0: + raise newException(OSError, "FAILED: " & command) + +proc selfExec*(command: string) {. + raises: [OSError], tags: [ExecIOEffect, WriteIOEffect].} = + ## Executes an external command with the current nim/nimble executable. + ## `Command` must not contain the "nim " part. + let c = selfExe() & " " & command + log "exec: " & c: + if rawExec(c) != 0: + raise newException(OSError, "FAILED: " & c) + checkOsError() proc put*(key, value: string) = ## Sets a configuration 'key' like 'gcc.options.always' to its value. @@ -189,7 +299,7 @@ proc get*(key: string): string = builtin proc exists*(key: string): bool = - ## Checks for the existance of a configuration 'key' + ## Checks for the existence of a configuration 'key' ## like 'gcc.options.always'. builtin @@ -197,81 +307,146 @@ proc nimcacheDir*(): string = ## Retrieves the location of 'nimcache'. builtin +proc projectName*(): string = + ## Retrieves the name of the current project + builtin + +proc projectDir*(): string = + ## Retrieves the absolute directory of the current project + builtin + +proc projectPath*(): string = + ## Retrieves the absolute path of the current project + builtin + proc thisDir*(): string = - ## Retrieves the location of the current ``nims`` script file. + ## Retrieves the directory of the current `nims` script file. Its path is + ## obtained via `currentSourcePath` (although, currently, + ## `currentSourcePath` resolves symlinks, unlike `thisDir`). builtin proc cd*(dir: string) {.raises: [OSError].} = ## Changes the current directory. ## ## The change is permanent for the rest of the execution, since this is just - ## a shortcut for `os.setCurrentDir() - ## <http://nim-lang.org/os.html#setCurrentDir,string>`_ . Use the `withDir() - ## <#withDir>`_ template if you want to perform a temporary change only. + ## a shortcut for `os.setCurrentDir() <os.html#setCurrentDir,string>`_ . Use + ## the `withDir() <#withDir.t,string,untyped>`_ template if you want to + ## perform a temporary change only. setCurrentDir(dir) checkOsError() +proc findExe*(bin: string): string = + ## Searches for bin in the current working directory and then in directories + ## listed in the PATH environment variable. Returns "" if the exe cannot be + ## found. + builtin + template withDir*(dir: string; body: untyped): untyped = ## Changes the current directory temporarily. ## - ## If you need a permanent change, use the `cd() <#cd>`_ proc. Usage example: - ## - ## .. code-block:: nim + ## If you need a permanent change, use the `cd() <#cd,string>`_ proc. + ## Usage example: + ## ```nim + ## # inside /some/path/ ## withDir "foo": - ## # inside foo - ## #back to last dir - var curDir = getCurrentDir() + ## # move to /some/path/foo/ + ## # back in /some/path/ + ## ``` + let curDir = getCurrentDir() try: cd(dir) body finally: cd(curDir) -template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 - proc writeTask(name, desc: string) = if desc.len > 0: var spaces = " " for i in 0 ..< 20 - name.len: spaces.add ' ' echo name, spaces, desc -template task*(name: untyped; description: string; body: untyped): untyped = - ## Defines a task. Hidden tasks are supported via an empty description. - ## Example: - ## - ## .. code-block:: nim - ## task build, "default build is via the C backend": - ## setCommand "c" - proc `name Task`() = body - - let cmd = getCommand() - if cmd.len == 0 or cmd ==? "help": - setCommand "help" - writeTask(astToStr(name), description) - elif cmd ==? astToStr(name): - setCommand "nop" - `name Task`() +proc cppDefine*(define: string) = + ## tell Nim that `define` is a C preprocessor `#define` and so always + ## needs to be mangled. + builtin -var - packageName* = "" ## Nimble support: Set this to the package name. It - ## is usually not required to do that, nims' filename is - ## the default. - version*: string ## Nimble support: The package's version. - author*: string ## Nimble support: The package's author. - description*: string ## Nimble support: The package's description. - license*: string ## Nimble support: The package's license. - srcdir*: string ## Nimble support: The package's source directory. - binDir*: string ## Nimble support: The package's binary directory. - backend*: string ## Nimble support: The package's backend. - - skipDirs*, skipFiles*, skipExt*, installDirs*, installFiles*, - installExt*, bin*: seq[string] = @[] ## Nimble metadata. - requiresData*: seq[string] = @[] ## Exposes the list of requirements for read - ## and write accesses. - -proc requires*(deps: varargs[string]) = - ## Nimble support: Call this to set the list of requirements of your Nimble - ## package. - for d in deps: requiresData.add(d) +proc stdinReadLine(): string {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc stdinReadAll(): string {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc readLineFromStdin*(): string {.raises: [IOError].} = + ## Reads a line of data from stdin - blocks until \n or EOF which happens when stdin is closed + log "readLineFromStdin": + result = stdinReadLine() + checkError(EOFError) + +proc readAllFromStdin*(): string {.raises: [IOError].} = + ## Reads all data from stdin - blocks until EOF which happens when stdin is closed + log "readAllFromStdin": + result = stdinReadAll() + checkError(EOFError) + +when not defined(nimble): + template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 + template task*(name: untyped; description: string; body: untyped): untyped = + ## Defines a task. Hidden tasks are supported via an empty description. + ## + ## Example: + ## ```nim + ## task build, "default build is via the C backend": + ## setCommand "c" + ## ``` + ## + ## For a task named `foo`, this template generates a `proc` named + ## `fooTask`. This is useful if you need to call one task in + ## another in your Nimscript. + ## + ## Example: + ## + ## ```nim + ## task foo, "foo": # > nim foo + ## echo "Running foo" # Running foo + ## + ## task bar, "bar": # > nim bar + ## echo "Running bar" # Running bar + ## fooTask() # Running foo + ## ``` + proc `name Task`*() = + setCommand "nop" + body + + let cmd = getCommand() + if cmd.len == 0 or cmd ==? "help": + setCommand "help" + writeTask(astToStr(name), description) + elif cmd ==? astToStr(name): + `name Task`() + + # nimble has its own implementation for these things. + var + packageName* = "" ## Nimble support: Set this to the package name. It + ## is usually not required to do that, nims' filename is + ## the default. + version*: string ## Nimble support: The package's version. + author*: string ## Nimble support: The package's author. + description*: string ## Nimble support: The package's description. + license*: string ## Nimble support: The package's license. + srcDir*: string ## Nimble support: The package's source directory. + binDir*: string ## Nimble support: The package's binary directory. + backend*: string ## Nimble support: The package's backend. + + skipDirs*, skipFiles*, skipExt*, installDirs*, installFiles*, + installExt*, bin*: seq[string] = @[] ## Nimble metadata. + requiresData*: seq[string] = @[] ## Exposes the list of requirements for read + ## and write accesses. + + proc requires*(deps: varargs[string]) = + ## Nimble support: Call this to set the list of requirements of your Nimble + ## package. + for d in deps: requiresData.add(d) {.pop.} |