import std/[algorithm, dirs, logging, options, paths, strutils, syncio] import strformat import commandant type ErrorCode = typeof(QuitSuccess) MainResult* = object output*: seq[string] errorCode*: ErrorCode const version = "1.0.1" const usage_string = """usage: pathsd [--version | --help] [-v | -q] [-s shell] search_path ...""" const version_string = fmt"""pathsd {version}""" const help_string = fmt"""pathsd {version} by BTHLabs Developed by Tomek Wójcik (https://bthlabs.pl/) (c) 2023-present by BTHLabs | MIT License Usage: pathsd [options] search_path ... Options: -s --shell target shell (defaults to bash) -v --verbose turn debugging messages on -q --quiet supress all logging messages --version show the version --help show this help""" const QuitInvalidArgs: ErrorCode = 64 var logger*: ConsoleLogger = newConsoleLogger( fmtStr = "$datetime $appname $levelname: ", levelThreshold = lvlInfo, useStderr = true, ) proc handleCommandantError(reason: ExitReason, msg: string = "", token: Option[CmdToken] = none(CmdToken), ) = case reason of ExitReason.missingArgumentValue, ExitReason.missingOptionValue: quit(usage_string, QuitInvalidArgs) of ExitReason.exception: logger.log(lvlError, msg) quit(QuitFailure) proc readPart(path: Path): seq[string] = logger.log(lvlDebug, fmt"Processing part: {path.string}") var pathFile: File = open(path.string) result = @[] try: var line: string = readLine(pathFile) while line != "": result.add(line) line = readLine(pathFile) except EOFError as exception: discard finally: close(pathFile) return result proc main*(searchPaths: seq[string]): MainResult = var parts: seq[string] = @[] logger.log(lvlDebug, fmt"Processing search paths: {searchPaths}") for searchPath in searchPaths: var searchPathPath: Path = Path(searchPath) if not dirExists(searchPathPath): logger.log(lvlNotice, fmt"Skipping {searchPath}: does not exist?") continue var partPaths: seq[Path] = @[] for pathComponent, path in walkDir(searchPathPath, true, false, true): if pathComponent in [PathComponent.pcFile, PathComponent.pcLinkToFile]: partPaths.add(searchPathPath / path) partPaths.sort do (x: Path, y: Path) -> int: result = cmp(x.string, y.string) for partPath in partPaths: parts = parts & readPart(partPath) return MainResult(output: parts, errorCode: QuitSuccess) when isMainModule: commandline: arguments(searchPaths, string, true) commandant.option(shell, string, "shell", "s", "bash") flag(verbose, "verbose", "v") flag(quiet, "quiet", "q") exitoption("help", "h", help_string, QuitFailure) exitoption("version", "", version_string, QuitFailure) errorproc(handleCommandantError) var loggerLevel: Level = lvlInfo if quiet: loggerLevel = lvlNone elif verbose: loggerLevel = lvlDebug logger.levelThreshold = loggerLevel var mainResult = main(searchPaths) if mainResult.output != @[] and mainResult.errorCode == QuitSuccess: var output: string = "" case shell of "bash": let pathComponents = join(mainResult.output, ":") output = fmt"""export PATH="{pathComponents}":$PATH""" if output != "": echo(output) quit(mainResult.errorCode)