all repos — litestore @ 2221204d6f84d20280020d51831f2a5d2c6b3ecf

A minimalist nosql document store.

Modified direcory structure to be nimble compliant.
h3rald h3rald@h3rald.com
Sat, 22 Sep 2018 15:18:55 +0200
commit

2221204d6f84d20280020d51831f2a5d2c6b3ecf

parent

caa4649c94c27f6423822d2d0d615671969a7616

112 files changed, 597 insertions(+), 609 deletions(-)

jump to
M .travis.yml.travis.yml

@@ -36,9 +36,7 @@ cd ..

before_script: - export PATH="nim-$BRANCH/bin${PATH:+:$PATH}" script: - - nim c --cc:$CC --verbosity:0 litestore.nim - # Optional: build docs. - #- nim doc --docSeeSrcUrl:https://github.com/h3rald/litestore/blob/master --project litestore.nim + - nim c --cc:$CC --verbosity:0 src/litestore.nim cache: directories: - nim-master
M build_guidebuild_guide

@@ -16,7 +16,7 @@ md/nim-api_high.md

md/nim-api_low.md md/credits.md ) -cd admin +cd src/admin for page in ${pages[@]} do (cat "${page}"; printf "\n\n") >> LiteStore_UserGuide.md

@@ -24,4 +24,4 @@ done

hastyscribe --field/version:1.4.1 LiteStore_UserGuide.md rm LiteStore_UserGuide.md mv LiteStore_UserGuide.htm .. -cd .. +cd ../..
D lib/cli.nim

@@ -1,174 +0,0 @@

-import - parseopt, - strutils, - strtabs -import - logger, - config, - types, - utils - -const favicon = "../admin/favicon.ico".slurp - -var - operation = opRun - directory:string = "" - readonly = false - logLevel = "warn" - mount = false - exOperation:string = "" - exFile:string = "" - exBody:string = "" - exType:string = "" - exUri:string = "" - -let - usage* = appname & " v" & version & " - Lightweight REST Document Store" & """ - -(c) 2015-2018 Fabio Cevasco - - Usage: - litestore [command] [option1 option2 ...] - - Commands: - run Start LiteStore server (default if no command specified). - delete Delete a previously-imported specified directory (requires -d). - execute Execute an operation on data stored in the datastore (requires -o, -u, and in certain cases -f or -b and -t). - import Import the specified directory into the datastore (requires -d). - export Export the previously-imported specified directory to the current directory (requires -d). - optimize Optimize search indexes. - vacuum Vacuum datastore. - - Options: - -a, --address Specify server address (default: 127.0.0.1). - -b, --body Specify a string containing input data for an operation to be executed. - -d, --directory Specify a directory to serve, import, export, delete, or mount. - -f, --file Specify a file containing input data for an operation to be executed. - -h, --help Display this message. - -l, --log Specify the log level: debug, info, warn, error, none (default: info) - -m, --mount Mirror database changes to the specified directory on the filesystem. - -o, --operation Specify an operation to execute via the execute command: get, put, delete, patch, post, head, options. - -p, --port Specify server port number (default: 9500). - -r, --readonly Allow only data retrieval operations. - -s, --store Specify a datastore file (default: data.db) - -t, --type Specify a content type for the body an operation to be executed via the execute command. - -u, --uri Specify an uri to execute an operation through the execute command. - -v, --version Display the program version. -""" - -for kind, key, val in getOpt(): - case kind: - of cmdArgument: - case key: - of "run": - operation = opRun - of "import": - operation = opImport - of "execute": - operation = opExecute - of "export": - operation = opExport - of "delete": - operation = opDelete - of "optimize": - operation = opOptimize - of "vacuum": - operation = opVacuum - else: - discard - of cmdLongOption, cmdShortOption: - case key: - of "address", "a": - if val == "": - fail(100, "Address not specified.") - address = val - of "port", "p": - if val == "": - fail(101, "Port not specified.") - port = val.parseInt - of "store", "s": - file = val - of "log", "l": - if val == "": - fail(102, "Log level not specified.") - case val: - of "info": - LOG.level = lvInfo - of "warn": - LOG.level = lvWarn - of "debug": - LOG.level = lvDebug - of "error": - LOG.level = lvError - of "none": - LOG.level = lvNone - else: - fail(103, "Invalid log level '$1'" % val) - loglevel = val - of "directory", "d": - if val == "": - fail(104, "Directory not specified.") - directory = val - of "operation", "o": - if val == "": - fail(106, "Operation not specified.") - exOperation = val - of "file", "f": - if val == "": - fail(107, "File not specified.") - exFile = val - of "uri", "u": - if val == "": - fail(108, "URI not specified.") - exUri = val - of "body", "b": - if val == "": - fail(112, "Body not specified.") - exBody = val - of "type", "t": - if val == "": - fail(113, "Content type not specified.") - exType = val - of "mount", "m": - mount = true - of "version", "v": - echo version - quit(0) - of "help", "h": - echo usage - quit(0) - of "readonly", "r": - readonly = true - else: - discard - else: - discard - -# Validation - -if directory == "" and (operation in [opDelete, opImport, opExport] or mount): - fail(105, "--directory option not specified.") - -if exFile == "" and (exOperation in ["put", "post", "patch"]): - fail(109, "--file option not specified") - -if exUri == "" and operation == opExecute: - fail(110, "--uri option not specified") - -if exOperation == "" and operation == opExecute: - fail(111, "--operation option not specified") - -LS.operation = operation -LS.address = address -LS.port = port -LS.file = file -LS.directory = directory -LS.readonly = readonly -LS.favicon = favicon -LS.loglevel = loglevel -LS.mount = mount -LS.execution.file = exFile -LS.execution.body = exBody -LS.execution.ctype = exType -LS.execution.uri = exUri -LS.execution.operation = exOperation
D lib/config.nim

@@ -1,41 +0,0 @@

-import - parsecfg, - streams, - strutils - -const - cfgfile = "../litestore.nimble".slurp - -var - file* = "data.db" - address* = "127.0.0.1" - appname* = "LiteStore" - port* = 9500 - version*: string - f = newStringStream(cfgfile) - -if f != nil: - var p: CfgParser - open(p, f, "../litestore.nimble") - while true: - var e = next(p) - case e.kind - of cfgEof: - break - of cfgKeyValuePair: - case e.key: - of "version": - version = e.value - else: - discard - of cfgError: - stderr.writeLine("Configuration error.") - quit(1) - else: - discard - close(p) -else: - stderr.writeLine("Cannot process configuration file.") - quit(2) - -
D lib/types.nim

@@ -1,119 +0,0 @@

-import - x_db_sqlite, - asynchttpserver, - pegs, - strtabs -import - config - -type - EDatastoreExists* = object of Exception - EDatastoreDoesNotExist* = object of Exception - EDatastoreUnavailable* = object of Exception - EInvalidTag* = object of Exception - EDirectoryNotFound* = object of Exception - EFileNotFound* = object of Exception - EFileExists* = object of Exception - EInvalidRequest* = object of Exception - uarray* {.unchecked.} [T] = array[0..0, T] - ExecutionData* = object - operation*: string - file*: string - body*: string - ctype*: string - uri*: string - Datastore* = object - db*: DbConn - path*: string - mount*: string - QueryOptions* = object - tables*: seq[string] - jsonFilter*: string - jsonSelect*: seq[tuple[path: string, alias: string]] - select*: seq[string] - single*:bool - limit*: int - offset*: int - orderby*: string - tags*: string - like*: string - createdAfter*: string - createdBefore*: string - modifiedAfter*: string - modifiedBefore*: string - folder*: string - search*: string - TagExpression* = object - tag*: string - startswith*: bool - endswith*: bool - negated*: bool - Operation* = enum - opRun, - opImport, - opExport, - opDelete, - opVacuum, - opOptimize, - opExecute - LogLevel* = enum - lvDebug - lvInfo - lvWarn - lvError - lvNone - Logger* = object - level*: LogLevel - LiteStore* = object - store*: Datastore - execution*: ExecutionData - address*: string - port*: int - operation*: Operation - directory*: string - file*: string - mount*: bool - readonly*: bool - appname*: string - appversion*: string - favicon*:string - loglevel*:string - LSRequest* = asynchttpserver.Request - LSResponse* = tuple[ - code: HttpCode, - content: string, - headers: HttpHeaders] - ResourceInfo* = tuple[ - resource: string, - id: string, - version: string - ] - -var - PEG_TAG* {.threadvar.}: Peg - PEG_USER_TAG* {.threadvar.}: Peg - PEG_DEFAULT_URL* {.threadvar.}: Peg - PEG_URL* {.threadvar.}: Peg - -PEG_TAG = peg"""^\$? [a-zA-Z0-9_\-?~:.@#^!+]+$""" -PEG_USER_TAG = peg"""^[a-zA-Z0-9_\-?~:.@#^!+]+$""" -PEG_DEFAULT_URL = peg"""^\/{(docs / info / dir / tags)} (\/ {(.+)} / \/?)$""" -PEG_URL = peg"""^\/({(v\d+)} \/) {([^\/]+)} (\/ {(.+)} / \/?)$""" - -# Initialize LiteStore -var LS* {.threadvar.}: LiteStore -var TAB_HEADERS* {.threadvar.}: array[0..2, (string, string)] - -LS.appversion = version -LS.appname = appname - -TAB_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": "Content-Type", - "Server": LS.appname & "/" & LS.appversion -} - -proc newQueryOptions*(): QueryOptions = - return QueryOptions(select: @["documents.id AS id", "documents.data AS data", "content_type", "binary", "searchable", "created", "modified"], - single: false, limit: 0, offset: 0, orderby: "", tags: "", search: "", folder: "", like: "", - createdAfter: "", createdBefore: "", modifiedAfter: "", modifiedBefore: "", jsonFilter: "", jsonSelect: newSeq[tuple[path: string, alias: string]](), tables: newSeq[string]())
D litestore.nim

@@ -1,189 +0,0 @@

-import - lib/x_sqlite3, - lib/x_db_sqlite as db, - strutils, - os, - oids, - times, - json, - pegs, - uri, - strtabs, - httpcore, - cgi, - base64 -import - lib/types, - lib/logger, - lib/utils, - lib/core, - lib/cli, - lib/server - -export - types, - server, - logger, - utils - -from asyncdispatch import runForever - -{.compile: "vendor/sqlite/libsqlite3.c".} -{.passC: "-DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_JSON1".} - -proc executeOperation*() = - let file = LS.execution.file - let body = LS.execution.body - let ctype = LS.execution.ctype - let uri = LS.execution.uri - let operation = LS.execution.operation - var req:LSRequest - case operation.toUpperAscii: - of "GET": - req.reqMethod = HttpGet - of "POST": - req.reqMethod = HttpPost - of "PUT": - req.reqMethod = HttpPut - of "PATCH": - req.reqMethod = HttpPatch - of "DELETE": - req.reqMethod = HttpDelete - of "OPTIONS": - req.reqMethod = HttpOptions - of "HEAD": - req.reqMethod = HttpHead - else: - fail(203, "Operation '$1' is not supported" % [operation]) - if body.len > 0: - req.body = body - elif file.len > 0: - req.body = file.readFile - req.headers = newHttpHeaders() - if ctype.len > 0: - req.headers["Content-Type"] = ctype - req.hostname = "<cli>" - req.url = parseUri("$1://$2:$3/$4" % @["http", "localhost", "9500", uri]) - let resp = req.process(LS) - echo resp.content - if resp.code.int < 300 and resp.code.int >= 200: - quit(0) - else: - quit(resp.code.int) - -proc setup*(open = true) = - if not LS.file.fileExists: - try: - LOG.debug("Creating datastore: ", LS.file) - LS.file.createDatastore() - except: - eWarn() - fail(200, "Unable to create datastore '$1'" % [LS.file]) - if (open): - try: - LS.store = LS.file.openDatastore() - if LS.mount: - try: - LS.store.mountDir(LS.directory) - except: - eWarn() - fail(202, "Unable to mount directory '$1'" % [LS.directory]) - except: - fail(201, "Unable to open datastore '$1'" % [LS.file]) - -when isMainModule: - - # Manage vacuum operation separately - if LS.operation == opVacuum: - setup(false) - vacuum LS.file - else: - # Open Datastore - setup(true) - - case LS.operation: - of opRun: - LS.serve - runForever() - of opImport: - LS.store.importDir(LS.directory) - of opExport: - LS.store.exportDir(LS.directory) - of opDelete: - LS.store.deleteDir(LS.directory) - of opOptimize: - LS.store.optimize - of opExecute: - executeOperation() - else: - discard - -else: - - proc params*(query: string): StringTableRef = - new(result) - let pairs = query.split("&") - for pair in pairs: - let data = pair.split("=") - result[data[0]] = data[1] - - proc query*(table: StringTableRef): string = - var params = newSeq[string](0) - for key, value in pairs(table): - params.add("$1=$2" % @[key, value]) - return params.join("&") - - proc newLSRequest(meth: HttpMethod, resource, id, body = "", params = newStringTable(), headers = newHttpHeaders()): LSRequest = - result.reqMethod = meth - result.body = body - result.headers = headers - result.url = parseUri("$1://$2:$3/$4/$5?$6" % @["http", "localhost", "9500", resource, id, params.query()]) - - # Public API: Low-level - - proc getInfo*(): LSResponse = - return LS.getInfo() - - proc getRawDocuments*(options = newQueryOptions()): LSResponse = - return LS.getRawDocuments(options) - - proc getDocument*(id: string, options = newQueryOptions()): LSResponse = - return LS.getDocument(id, options) - - proc getRawDocument*(id: string, options = newQueryOptions()): LSResponse = - return LS.getRawDocument(id, options) - - proc deleteDocument*(id: string): LSResponse = - return LS.deleteDocument(id) - - proc postDocument*(body, ct: string, folder=""): LSResponse = - return LS.postDocument(body, ct, folder) - - proc putDocument*(id, body, ct: string): LSResponse = - return LS.putDocument(id, body, ct) - - proc patchDocument*(id, body: string): LSResponse = - return LS.patchDocument(id, body) - - # Public API: High-level - - proc get*(resource, id: string, params = newStringTable(), headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpGet, resource, id, "", params, headers).get(LS, resource, id) - - proc post*(resource, folder, body: string, headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpPost, resource, "", body, newStringTable(), headers).post(LS, resource, folder & "/") - - proc put*(resource, id, body: string, headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpPut, resource, id, body, newStringTable(), headers).put(LS, resource, id) - - proc patch*(resource, id, body: string, headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpPatch, resource, id, body, newStringTable(), headers).patch(LS, resource, id) - - proc delete*(resource, id: string, headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpPatch, resource, id, "", newStringTable(), headers).delete(LS, resource, id) - - proc head*(resource, id: string, headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpHead, resource, id, "", newStringTable(), headers).head(LS, resource, id) - - proc options*(resource, id: string, headers = newHttpHeaders()): LSResponse = - return newLSRequest(HttpOptions, resource, id, "", newStringTable(), headers).options(LS, resource, id)
M litestore.nimblelitestore.nimble

@@ -1,12 +1,100 @@

-[Package] -name = "litestore" -version = "1.5.0" -author = "Fabio Cevasco" -description = "Self-contained, lightweight, RESTful document store." -license = "MIT" -bin = "litestore" -skipFiles = @["nakefile.nim"] -skipDirs = @["test"] +import + ospaths, + strutils + +template thisModuleFile: string = instantiationInfo(fullPaths = true).filename + +when fileExists(thisModuleFile.parentDir / "src/litestorepkg/lib/config.nim"): + # In the git repository the Nimble sources are in a ``src`` directory. + import src/litestorepkg/lib/config +else: + # When the package is installed, the ``src`` directory disappears. + import litestorepkg/lib/config + +# Package + +version = pkgVersion +author = pkgAuthor +description = pkgDescription +license = pkgLicense +bin = @[pkgName] +srcDir = "src" + +# Dependencies + +requires "nim >= 0.18.0" + +# Build + +const + parallel = "" #"--parallelBuild:1 --verbosity:3" + compile = "nim c -d:release --threads:on" & " " & parallel + linux_x86 = "--cpu:i386 --os:linux" + linux_x64 = "--cpu:amd64 --os:linux" + linux_arm = "--cpu:arm --os:linux" + windows_x86 = "--cpu:i386 --os:windows" + windows_x64 = "--cpu:amd64 --os:windows" + macosx_x64 = "" + ls = "litestore" + doc = "LiteStore_UserGuide.htm" + db = "data.db" + ls_file = "litestore.nim" + zip = "zip -X" -[Deps] -requires: "nim >= 0.18.0" +proc shell(command, args = "", dest = "") = + exec command & " " & args & " " & dest + +proc filename_for(os: string, arch: string): string = + return "litestore" & "_v" & version & "_" & os & "_" & arch & ".zip" + +task windows_x86_build, "Build LiteStore for Windows (x86)": + shell compile, windows_x86, ls_file + +task windows_x64_build, "Build LiteStore for Windows (x64)": + shell compile, windows_x64, ls_file + +task linux_x64_build, "Build LiteStore for Linux (x64)": + shell compile, linux_x64, ls_file + +task linux_x86_build, "Build LiteStore for Linux (x86)": + shell compile, linux_x86, ls_file + +task linux_arm_build, "Build LiteStore for Linux (ARM)": + shell compile, linux_arm, ls_file + +task macosx_x64_build, "Build LiteStore for Mac OS X (x64)": + shell compile, macosx_x64, ls_file + +task release, "Release LiteStore": + echo "Generating Guide..." + shell "./build_guide" + echo "Preparing Data Store preloaded with Admin App..." + cd "src" + if db.existsFile: + db.rmFile + shell "litestore -d:admin import" + echo "\n\n\n WINDOWS - x86:\n\n" + windows_x86_buildTask() + shell zip, "$1 $2 $3 $4 $5" % [filename_for("windows", "x86"), ls & ".exe", doc, db] + shell "rm", ls & ".exe" + echo "\n\n\n WINDOWS - x64:\n\n" + windows_x64_buildTask() + shell zip, "$1 $2 $3 $4 $5" % [filename_for("windows", "x64"), ls & ".exe", doc, db] + shell "rm", ls & ".exe" + echo "\n\n\n LINUX - x64:\n\n" + linux_x64_buildTask() + shell zip, "$1 $2 $3 $4 $5" % [filename_for("linux", "x64"), ls, doc, db] + shell "rm", ls + echo "\n\n\n LINUX - x86:\n\n" + linux_x86_buildTask() + shell zip, "$1 $2 $3 $4 $5" % [filename_for("linux", "x86"), ls, doc, db] + shell "rm", ls + echo "\n\n\n LINUX - ARM:\n\n" + linux_arm_buildTask() + shell zip, "$1 $2 $3 $4 $5" % [filename_for("linux", "arm"), ls, doc, db] + shell "rm", ls + echo "\n\n\n MAC OS X - x64:\n\n" + macosx_x64_buildTask() + shell zip, "$1 $2 $3 $4 $5" % [filename_for("macosx", "x64"), ls, doc, db] + shell "rm", ls + echo "\n\n\n ALL DONE!"
D nakefile.nim

@@ -1,70 +0,0 @@

-import nake -import lib/config - -const - parallel = "" #"--parallelBuild:1 --verbosity:3" - compile = "nim c -d:release --threads:on" & " " & parallel - linux_x86 = "--cpu:i386 --os:linux" - linux_x64 = "--cpu:amd64 --os:linux" - linux_arm = "--cpu:arm --os:linux" - windows_x86 = "--cpu:i386 --os:windows" - windows_x64 = "--cpu:amd64 --os:windows" - macosx_x64 = "" - ls = "litestore" - doc = "LiteStore_UserGuide.htm" - db = "data.db" - ls_file = "litestore.nim" - zip = "zip -X" - -proc filename_for(os: string, arch: string): string = - return "litestore" & "_v" & version & "_" & os & "_" & arch & ".zip" - -task "windows-x86-build", "Build LiteStore for Windows (x86)": - direshell compile, windows_x86, ls_file - -task "windows-x64-build", "Build LiteStore for Windows (x64)": - direshell compile, windows_x64, ls_file - -task "linux-x64-build", "Build LiteStore for Linux (x64)": - direshell compile, linux_x64, ls_file - -task "linux-x86-build", "Build LiteStore for Linux (x86)": - direshell compile, linux_x86, ls_file - -task "linux-arm-build", "Build LiteStore for Linux (ARM)": - direshell compile, linux_arm, ls_file - -task "macosx-x64-build", "Build LiteStore for Mac OS X (x64)": - direshell compile, macosx_x64, ls_file - -task "release", "Release LiteStore": - echo "Generating Guide..." - direshell "./build_guide" - echo "Preparing Data Store preloaded with Admin App..." - direshell "rm " & db - direshell "litestore -d:admin import" - echo "\n\n\n WINDOWS - x86:\n\n" - runTask "windows-x86-build" - direshell zip, filename_for("windows", "x86"), ls & ".exe", doc, db - direshell "rm", ls & ".exe" - echo "\n\n\n WINDOWS - x64:\n\n" - runTask "windows-x64-build" - direshell zip, filename_for("windows", "x64"), ls & ".exe", doc, db - direshell "rm", ls & ".exe" - echo "\n\n\n LINUX - x64:\n\n" - runTask "linux-x64-build" - direshell zip, filename_for("linux", "x64"), ls, doc, db - direshell "rm", ls - echo "\n\n\n LINUX - x86:\n\n" - runTask "linux-x86-build" - direshell zip, filename_for("linux", "x86"), ls, doc, db - direshell "rm", ls - echo "\n\n\n LINUX - ARM:\n\n" - runTask "linux-arm-build" - direshell zip, filename_for("linux", "arm"), ls, doc, db - direshell "rm", ls - echo "\n\n\n MAC OS X - x64:\n\n" - runTask "macosx-x64-build" - direshell zip, filename_for("macosx", "x64"), ls, doc, db - direshell "rm", ls - echo "\n\n\n ALL DONE!"
A src/litestore.nim

@@ -0,0 +1,189 @@

+import + litestorepkg/lib/x_sqlite3, + litestorepkg/lib/x_db_sqlite as db, + strutils, + os, + oids, + times, + json, + pegs, + uri, + strtabs, + httpcore, + cgi, + base64 +import + litestorepkg/lib/types, + litestorepkg/lib/logger, + litestorepkg/lib/utils, + litestorepkg/lib/core, + litestorepkg/lib/cli, + litestorepkg/lib/server + +export + types, + server, + logger, + utils + +from asyncdispatch import runForever + +{.compile: "vendor/sqlite/libsqlite3.c".} +{.passC: "-DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_JSON1".} + +proc executeOperation*() = + let file = LS.execution.file + let body = LS.execution.body + let ctype = LS.execution.ctype + let uri = LS.execution.uri + let operation = LS.execution.operation + var req:LSRequest + case operation.toUpperAscii: + of "GET": + req.reqMethod = HttpGet + of "POST": + req.reqMethod = HttpPost + of "PUT": + req.reqMethod = HttpPut + of "PATCH": + req.reqMethod = HttpPatch + of "DELETE": + req.reqMethod = HttpDelete + of "OPTIONS": + req.reqMethod = HttpOptions + of "HEAD": + req.reqMethod = HttpHead + else: + fail(203, "Operation '$1' is not supported" % [operation]) + if body.len > 0: + req.body = body + elif file.len > 0: + req.body = file.readFile + req.headers = newHttpHeaders() + if ctype.len > 0: + req.headers["Content-Type"] = ctype + req.hostname = "<cli>" + req.url = parseUri("$1://$2:$3/$4" % @["http", "localhost", "9500", uri]) + let resp = req.process(LS) + echo resp.content + if resp.code.int < 300 and resp.code.int >= 200: + quit(0) + else: + quit(resp.code.int) + +proc setup*(open = true) = + if not LS.file.fileExists: + try: + LOG.debug("Creating datastore: ", LS.file) + LS.file.createDatastore() + except: + eWarn() + fail(200, "Unable to create datastore '$1'" % [LS.file]) + if (open): + try: + LS.store = LS.file.openDatastore() + if LS.mount: + try: + LS.store.mountDir(LS.directory) + except: + eWarn() + fail(202, "Unable to mount directory '$1'" % [LS.directory]) + except: + fail(201, "Unable to open datastore '$1'" % [LS.file]) + +when isMainModule: + + # Manage vacuum operation separately + if LS.operation == opVacuum: + setup(false) + vacuum LS.file + else: + # Open Datastore + setup(true) + + case LS.operation: + of opRun: + LS.serve + runForever() + of opImport: + LS.store.importDir(LS.directory) + of opExport: + LS.store.exportDir(LS.directory) + of opDelete: + LS.store.deleteDir(LS.directory) + of opOptimize: + LS.store.optimize + of opExecute: + executeOperation() + else: + discard + +else: + + proc params*(query: string): StringTableRef = + new(result) + let pairs = query.split("&") + for pair in pairs: + let data = pair.split("=") + result[data[0]] = data[1] + + proc query*(table: StringTableRef): string = + var params = newSeq[string](0) + for key, value in pairs(table): + params.add("$1=$2" % @[key, value]) + return params.join("&") + + proc newLSRequest(meth: HttpMethod, resource, id, body = "", params = newStringTable(), headers = newHttpHeaders()): LSRequest = + result.reqMethod = meth + result.body = body + result.headers = headers + result.url = parseUri("$1://$2:$3/$4/$5?$6" % @["http", "localhost", "9500", resource, id, params.query()]) + + # Public API: Low-level + + proc getInfo*(): LSResponse = + return LS.getInfo() + + proc getRawDocuments*(options = newQueryOptions()): LSResponse = + return LS.getRawDocuments(options) + + proc getDocument*(id: string, options = newQueryOptions()): LSResponse = + return LS.getDocument(id, options) + + proc getRawDocument*(id: string, options = newQueryOptions()): LSResponse = + return LS.getRawDocument(id, options) + + proc deleteDocument*(id: string): LSResponse = + return LS.deleteDocument(id) + + proc postDocument*(body, ct: string, folder=""): LSResponse = + return LS.postDocument(body, ct, folder) + + proc putDocument*(id, body, ct: string): LSResponse = + return LS.putDocument(id, body, ct) + + proc patchDocument*(id, body: string): LSResponse = + return LS.patchDocument(id, body) + + # Public API: High-level + + proc get*(resource, id: string, params = newStringTable(), headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpGet, resource, id, "", params, headers).get(LS, resource, id) + + proc post*(resource, folder, body: string, headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpPost, resource, "", body, newStringTable(), headers).post(LS, resource, folder & "/") + + proc put*(resource, id, body: string, headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpPut, resource, id, body, newStringTable(), headers).put(LS, resource, id) + + proc patch*(resource, id, body: string, headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpPatch, resource, id, body, newStringTable(), headers).patch(LS, resource, id) + + proc delete*(resource, id: string, headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpPatch, resource, id, "", newStringTable(), headers).delete(LS, resource, id) + + proc head*(resource, id: string, headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpHead, resource, id, "", newStringTable(), headers).head(LS, resource, id) + + proc options*(resource, id: string, headers = newHttpHeaders()): LSResponse = + return newLSRequest(HttpOptions, resource, id, "", newStringTable(), headers).options(LS, resource, id)
A src/litestorepkg/lib/cli.nim

@@ -0,0 +1,174 @@

+import + parseopt, + strutils, + strtabs +import + logger, + config, + types, + utils + +const favicon = "../../admin/favicon.ico".slurp + +var + operation = opRun + directory:string = "" + readonly = false + logLevel = "warn" + mount = false + exOperation:string = "" + exFile:string = "" + exBody:string = "" + exType:string = "" + exUri:string = "" + +let + usage* = appname & " v" & pkgVersion & " - Lightweight REST Document Store" & """ + +(c) 2015-2018 Fabio Cevasco + + Usage: + litestore [command] [option1 option2 ...] + + Commands: + run Start LiteStore server (default if no command specified). + delete Delete a previously-imported specified directory (requires -d). + execute Execute an operation on data stored in the datastore (requires -o, -u, and in certain cases -f or -b and -t). + import Import the specified directory into the datastore (requires -d). + export Export the previously-imported specified directory to the current directory (requires -d). + optimize Optimize search indexes. + vacuum Vacuum datastore. + + Options: + -a, --address Specify server address (default: 127.0.0.1). + -b, --body Specify a string containing input data for an operation to be executed. + -d, --directory Specify a directory to serve, import, export, delete, or mount. + -f, --file Specify a file containing input data for an operation to be executed. + -h, --help Display this message. + -l, --log Specify the log level: debug, info, warn, error, none (default: info) + -m, --mount Mirror database changes to the specified directory on the filesystem. + -o, --operation Specify an operation to execute via the execute command: get, put, delete, patch, post, head, options. + -p, --port Specify server port number (default: 9500). + -r, --readonly Allow only data retrieval operations. + -s, --store Specify a datastore file (default: data.db) + -t, --type Specify a content type for the body an operation to be executed via the execute command. + -u, --uri Specify an uri to execute an operation through the execute command. + -v, --version Display the program version. +""" + +for kind, key, val in getOpt(): + case kind: + of cmdArgument: + case key: + of "run": + operation = opRun + of "import": + operation = opImport + of "execute": + operation = opExecute + of "export": + operation = opExport + of "delete": + operation = opDelete + of "optimize": + operation = opOptimize + of "vacuum": + operation = opVacuum + else: + discard + of cmdLongOption, cmdShortOption: + case key: + of "address", "a": + if val == "": + fail(100, "Address not specified.") + address = val + of "port", "p": + if val == "": + fail(101, "Port not specified.") + port = val.parseInt + of "store", "s": + file = val + of "log", "l": + if val == "": + fail(102, "Log level not specified.") + case val: + of "info": + LOG.level = lvInfo + of "warn": + LOG.level = lvWarn + of "debug": + LOG.level = lvDebug + of "error": + LOG.level = lvError + of "none": + LOG.level = lvNone + else: + fail(103, "Invalid log level '$1'" % val) + loglevel = val + of "directory", "d": + if val == "": + fail(104, "Directory not specified.") + directory = val + of "operation", "o": + if val == "": + fail(106, "Operation not specified.") + exOperation = val + of "file", "f": + if val == "": + fail(107, "File not specified.") + exFile = val + of "uri", "u": + if val == "": + fail(108, "URI not specified.") + exUri = val + of "body", "b": + if val == "": + fail(112, "Body not specified.") + exBody = val + of "type", "t": + if val == "": + fail(113, "Content type not specified.") + exType = val + of "mount", "m": + mount = true + of "version", "v": + echo pkgVersion + quit(0) + of "help", "h": + echo usage + quit(0) + of "readonly", "r": + readonly = true + else: + discard + else: + discard + +# Validation + +if directory == "" and (operation in [opDelete, opImport, opExport] or mount): + fail(105, "--directory option not specified.") + +if exFile == "" and (exOperation in ["put", "post", "patch"]): + fail(109, "--file option not specified") + +if exUri == "" and operation == opExecute: + fail(110, "--uri option not specified") + +if exOperation == "" and operation == opExecute: + fail(111, "--operation option not specified") + +LS.operation = operation +LS.address = address +LS.port = port +LS.file = file +LS.directory = directory +LS.readonly = readonly +LS.favicon = favicon +LS.loglevel = loglevel +LS.mount = mount +LS.execution.file = exFile +LS.execution.body = exBody +LS.execution.ctype = exType +LS.execution.uri = exUri +LS.execution.operation = exOperation
A src/litestorepkg/lib/config.nim

@@ -0,0 +1,13 @@

+const + pkgName* = "litestore" + pkgVersion* = "1.5.0" + pkgAuthor* = "Fabio Cevasco" + pkgDescription* = "Self-contained, lightweight, RESTful document store." + pkgLicense* = "MIT" + appname* = "LiteStore" + +var + file* = "data.db" + address* = "127.0.0.1" + port* = 9500 +
A src/litestorepkg/lib/types.nim

@@ -0,0 +1,119 @@

+import + x_db_sqlite, + asynchttpserver, + pegs, + strtabs +import + config + +type + EDatastoreExists* = object of Exception + EDatastoreDoesNotExist* = object of Exception + EDatastoreUnavailable* = object of Exception + EInvalidTag* = object of Exception + EDirectoryNotFound* = object of Exception + EFileNotFound* = object of Exception + EFileExists* = object of Exception + EInvalidRequest* = object of Exception + uarray* {.unchecked.} [T] = array[0..0, T] + ExecutionData* = object + operation*: string + file*: string + body*: string + ctype*: string + uri*: string + Datastore* = object + db*: DbConn + path*: string + mount*: string + QueryOptions* = object + tables*: seq[string] + jsonFilter*: string + jsonSelect*: seq[tuple[path: string, alias: string]] + select*: seq[string] + single*:bool + limit*: int + offset*: int + orderby*: string + tags*: string + like*: string + createdAfter*: string + createdBefore*: string + modifiedAfter*: string + modifiedBefore*: string + folder*: string + search*: string + TagExpression* = object + tag*: string + startswith*: bool + endswith*: bool + negated*: bool + Operation* = enum + opRun, + opImport, + opExport, + opDelete, + opVacuum, + opOptimize, + opExecute + LogLevel* = enum + lvDebug + lvInfo + lvWarn + lvError + lvNone + Logger* = object + level*: LogLevel + LiteStore* = object + store*: Datastore + execution*: ExecutionData + address*: string + port*: int + operation*: Operation + directory*: string + file*: string + mount*: bool + readonly*: bool + appname*: string + appversion*: string + favicon*:string + loglevel*:string + LSRequest* = asynchttpserver.Request + LSResponse* = tuple[ + code: HttpCode, + content: string, + headers: HttpHeaders] + ResourceInfo* = tuple[ + resource: string, + id: string, + version: string + ] + +var + PEG_TAG* {.threadvar.}: Peg + PEG_USER_TAG* {.threadvar.}: Peg + PEG_DEFAULT_URL* {.threadvar.}: Peg + PEG_URL* {.threadvar.}: Peg + +PEG_TAG = peg"""^\$? [a-zA-Z0-9_\-?~:.@#^!+]+$""" +PEG_USER_TAG = peg"""^[a-zA-Z0-9_\-?~:.@#^!+]+$""" +PEG_DEFAULT_URL = peg"""^\/{(docs / info / dir / tags)} (\/ {(.+)} / \/?)$""" +PEG_URL = peg"""^\/({(v\d+)} \/) {([^\/]+)} (\/ {(.+)} / \/?)$""" + +# Initialize LiteStore +var LS* {.threadvar.}: LiteStore +var TAB_HEADERS* {.threadvar.}: array[0..2, (string, string)] + +LS.appversion = pkgVersion +LS.appname = appname + +TAB_HEADERS = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "Content-Type", + "Server": LS.appname & "/" & LS.appversion +} + +proc newQueryOptions*(): QueryOptions = + return QueryOptions(select: @["documents.id AS id", "documents.data AS data", "content_type", "binary", "searchable", "created", "modified"], + single: false, limit: 0, offset: 0, orderby: "", tags: "", search: "", folder: "", like: "", + createdAfter: "", createdBefore: "", modifiedAfter: "", modifiedBefore: "", jsonFilter: "", jsonSelect: newSeq[tuple[path: string, alias: string]](), tables: newSeq[string]())