Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
751b3222db | |||
7af0acce68 | |||
b1cc4fbe51 | |||
4e5152bed3 |
@ -1,9 +1,9 @@
|
|||||||
import nre, terminal, strtabs, tables, unicode
|
import nre, terminal, strtabs, tables, unicode
|
||||||
import strutils except toUpper, toLower
|
import strutils except toUpper, toLower
|
||||||
|
|
||||||
import ./cliutilspkg/config
|
import ./cliutils/config
|
||||||
import ./cliutilspkg/daemonize
|
import ./cliutils/daemonize
|
||||||
import ./cliutilspkg/procutil
|
import ./cliutils/procutil
|
||||||
|
|
||||||
export config
|
export config
|
||||||
export daemonize
|
export daemonize
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.7.0"
|
version = "0.8.1"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Helper functions for writing command line interfaces."
|
description = "Helper functions for writing command line interfaces."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires @["nim >= 1.6.0", "docopt >= 0.6.8"]
|
requires @["nim >= 1.6.0", "docopt >= 0.6.8"]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import docopt, json, nre, os, strtabs, strutils
|
import std/[json, nre, os, sequtils, strtabs, strutils]
|
||||||
|
import docopt
|
||||||
|
|
||||||
type
|
type
|
||||||
CombinedConfig* = object
|
CombinedConfig* = object
|
||||||
@ -34,6 +35,22 @@ template walkFieldDefs*(t: NimNode, body: untyped) =
|
|||||||
# cfgType.walkFieldDefs:
|
# cfgType.walkFieldDefs:
|
||||||
# let valLookup = quote do: `getVal`(
|
# let valLookup = quote do: `getVal`(
|
||||||
|
|
||||||
|
proc initCombinedConfig*(
|
||||||
|
filename: string,
|
||||||
|
docopt: Table[string, Value] = initTable[string, Value]()
|
||||||
|
): CombinedConfig =
|
||||||
|
|
||||||
|
result = CombinedConfig(docopt: docopt, json: parseFile(filename))
|
||||||
|
|
||||||
|
proc findConfigFile*(name: string, otherLocations: seq[string] = @[]): string =
|
||||||
|
let cfgLocations = @[
|
||||||
|
$getEnv(name.strip(chars = {'.'}).toUpper()),
|
||||||
|
$getEnv("HOME") / name ] & otherLocations
|
||||||
|
|
||||||
|
result = cfgLocations.foldl(if fileExists(b): b else: a, "")
|
||||||
|
if result == "" or not fileExists(result):
|
||||||
|
raise newException(ValueError, "could not find configuration file")
|
||||||
|
|
||||||
proc getVal*(cfg: CombinedConfig, key: string): string =
|
proc getVal*(cfg: CombinedConfig, key: string): string =
|
||||||
let argKey = "--" & key
|
let argKey = "--" & key
|
||||||
let envKey = key.replace('-', '_').toUpper
|
let envKey = key.replace('-', '_').toUpper
|
||||||
@ -57,6 +74,20 @@ proc getVal*(cfg: CombinedConfig, key, default: string): string =
|
|||||||
try: return getVal(cfg, key)
|
try: return getVal(cfg, key)
|
||||||
except: return default
|
except: return default
|
||||||
|
|
||||||
|
proc getJson*(cfg: CombinedConfig, key: string): JsonNode =
|
||||||
|
let argKey = "--" & key
|
||||||
|
let envKey = key.replace('-', '_').toUpper
|
||||||
|
let jsonKey = key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper)
|
||||||
|
|
||||||
|
if cfg.docopt.contains(argKey) and cfg.docopt[argKey]: return parseJson($cfg.docopt[argKey])
|
||||||
|
elif existsEnv(envKey): return parseJson(getEnv(envKey))
|
||||||
|
elif cfg.json.hasKey(jsonKey): return cfg.json[jsonKey]
|
||||||
|
else: raise newException(ValueError, "cannot find a configuration value for \"" & key & "\"")
|
||||||
|
|
||||||
|
proc getJson*(cfg: CombinedConfig, key: string, default: JsonNode): JsonNode =
|
||||||
|
try: return getJson(cfg, key)
|
||||||
|
except: return default
|
||||||
|
|
||||||
proc loadEnv*(): StringTableRef =
|
proc loadEnv*(): StringTableRef =
|
||||||
result = newStringTable()
|
result = newStringTable()
|
||||||
|
|
94
cliutils/daemonize.nim
Normal file
94
cliutils/daemonize.nim
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import posix
|
||||||
|
import os except sleep
|
||||||
|
|
||||||
|
## Daemonize
|
||||||
|
var
|
||||||
|
pidFileInner: string
|
||||||
|
fi, fo, fe: File
|
||||||
|
|
||||||
|
proc onStop(sig: cint) {.noconv.} =
|
||||||
|
close(fi)
|
||||||
|
close(fo)
|
||||||
|
close(fe)
|
||||||
|
removeFile(pidFileInner)
|
||||||
|
|
||||||
|
quit(QuitSuccess)
|
||||||
|
|
||||||
|
proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
|
||||||
|
|
||||||
|
if fileExists(pidfile):
|
||||||
|
raise newException(IOError, "pidfile " & pidfile & " already exists, daemon already running?")
|
||||||
|
|
||||||
|
# We're about to do a little dance to end up with a process that is
|
||||||
|
# disconnected from the calling program's process group and is not a session
|
||||||
|
# leader (a daemon process).
|
||||||
|
#
|
||||||
|
# Remember, after a call to fork the code is now running in two processes,
|
||||||
|
# the parent process and the child process. So the returned PID will be a
|
||||||
|
# positive value if we are still on the parent process, and represents the
|
||||||
|
# PID of the newly created process. If we are on the newly created process,
|
||||||
|
# the PID returned with be 0.
|
||||||
|
|
||||||
|
let pid1 = fork()
|
||||||
|
|
||||||
|
# Are we the child process?
|
||||||
|
if pid1 == 0:
|
||||||
|
# Yes, so let's get ready to execute the main code given to us. Note that
|
||||||
|
# this child process will not actually live long. As part of our setup,
|
||||||
|
# we're going to create a new process session, resulting in this child
|
||||||
|
# process being the process group owner and session leader. We don't our
|
||||||
|
# daemon process to be a session leader because that allows it to be
|
||||||
|
# connected to a terminal. So after we've setup the new process group we
|
||||||
|
# will fork again and actually run the logic we're daemonizing in the
|
||||||
|
# grandchild process.
|
||||||
|
|
||||||
|
# Ignore whatever the original working directory was.
|
||||||
|
discard chdir("/")
|
||||||
|
|
||||||
|
# Create a new session to decouple us from the original terminal.
|
||||||
|
discard setsid()
|
||||||
|
|
||||||
|
# Set the process file mode creation mask to 0000
|
||||||
|
discard umask(0)
|
||||||
|
|
||||||
|
# Fork again so the grandchild process will not be the session leader.
|
||||||
|
let pid2 = fork()
|
||||||
|
|
||||||
|
# If we're still on the child process, we're done.
|
||||||
|
if pid2 > 0: quit(QuitSuccess)
|
||||||
|
|
||||||
|
# Otherwise, if we are the grandchild let's finish setting up our
|
||||||
|
# environment.
|
||||||
|
flushFile(stdout)
|
||||||
|
flushFile(stderr)
|
||||||
|
|
||||||
|
# Setup our STDIN, STDERR, and STDOUT pipes (if given)
|
||||||
|
if si.len > 0:
|
||||||
|
fi = open(si, fmRead)
|
||||||
|
discard dup2(getFileHandle(fi), getFileHandle(stdin))
|
||||||
|
|
||||||
|
if so.len > 0:
|
||||||
|
fo = open(so, fmAppend)
|
||||||
|
discard dup2(getFileHandle(fo), getFileHandle(stdout))
|
||||||
|
|
||||||
|
if se.len > 0:
|
||||||
|
fe = open(se, fmAppend)
|
||||||
|
discard dup2(getFileHandle(fe), getFileHandle(stderr))
|
||||||
|
|
||||||
|
# Keep a record of the PID filename in our process memory so we can
|
||||||
|
# reference it in our cleanup hooks.
|
||||||
|
pidFileInner = pidfile
|
||||||
|
|
||||||
|
# Add hooks to cleanup after ourselves when we're asked to die.
|
||||||
|
signal(SIGINT, onStop)
|
||||||
|
signal(SIGTERM, onStop)
|
||||||
|
|
||||||
|
# Find out what our actual PID is and save it to the PID file.
|
||||||
|
let childPid = getpid()
|
||||||
|
writeFile(pidfile, $childPid)
|
||||||
|
|
||||||
|
# Finally, execute the logic given to us to daemonize
|
||||||
|
daemonMain()
|
||||||
|
|
||||||
|
# If we're not the child proces, return the new PID of the daemonized process
|
||||||
|
return pid1
|
@ -1,68 +0,0 @@
|
|||||||
import posix
|
|
||||||
import os except sleep
|
|
||||||
|
|
||||||
## Daemonize
|
|
||||||
var
|
|
||||||
pidFileInner: string
|
|
||||||
fi, fo, fe: File
|
|
||||||
|
|
||||||
proc onStop(sig: cint) {.noconv.} =
|
|
||||||
close(fi)
|
|
||||||
close(fo)
|
|
||||||
close(fe)
|
|
||||||
removeFile(pidFileInner)
|
|
||||||
|
|
||||||
quit(QuitSuccess)
|
|
||||||
|
|
||||||
proc daemonize*(pidfile, si, so, se: string, daemonMain: proc(): void): Pid =
|
|
||||||
|
|
||||||
if fileExists(pidfile):
|
|
||||||
raise newException(IOError, "pidfile " & pidfile & " already exists, daemon already running?")
|
|
||||||
|
|
||||||
let pid1 = fork()
|
|
||||||
|
|
||||||
# Are we the child process?
|
|
||||||
if pid1 == 0:
|
|
||||||
|
|
||||||
# Yes, so let's get ready to execute the main code given to us.
|
|
||||||
discard chdir("/")
|
|
||||||
discard setsid()
|
|
||||||
discard umask(0)
|
|
||||||
|
|
||||||
# Fork again... but I'm not sure why.
|
|
||||||
let pid2 = fork()
|
|
||||||
|
|
||||||
# We don't need the intermediate process, so if we are not the child this
|
|
||||||
# time, lets just quit
|
|
||||||
if pid2 > 0: quit(QuitSuccess)
|
|
||||||
|
|
||||||
# If we are the grandchild let's set up our environment.
|
|
||||||
flushFile(stdout)
|
|
||||||
flushFile(stderr)
|
|
||||||
|
|
||||||
if si.len > 0:
|
|
||||||
fi = open(si, fmRead)
|
|
||||||
discard dup2(getFileHandle(fi), getFileHandle(stdin))
|
|
||||||
|
|
||||||
if so.len > 0:
|
|
||||||
fo = open(so, fmAppend)
|
|
||||||
discard dup2(getFileHandle(fo), getFileHandle(stdout))
|
|
||||||
|
|
||||||
if se.len > 0:
|
|
||||||
fe = open(se, fmAppend)
|
|
||||||
discard dup2(getFileHandle(fe), getFileHandle(stderr))
|
|
||||||
|
|
||||||
pidFileInner = pidfile
|
|
||||||
|
|
||||||
# Add hooks to cleanup after ourselves when we're asked to die.
|
|
||||||
signal(SIGINT, onStop)
|
|
||||||
signal(SIGTERM, onStop)
|
|
||||||
|
|
||||||
# Find out what our actual PID is and save it
|
|
||||||
let childPid = getpid()
|
|
||||||
writeFile(pidfile, $childPid)
|
|
||||||
|
|
||||||
# Finally, execute our main
|
|
||||||
daemonMain()
|
|
||||||
|
|
||||||
return pid1
|
|
Reference in New Issue
Block a user