├── .gitignore ├── .vscode └── settings.json ├── base ├── base.nim ├── base.nimble ├── base │ ├── basefs.nim │ ├── bitset.nim │ ├── check.nim │ ├── console_log.nim │ ├── doc.nim │ ├── enumm.nim │ ├── env.nim │ ├── fallible.nim │ ├── hash.nim │ ├── json.nim │ ├── license │ ├── log.nim │ ├── math.nim │ ├── option.nim │ ├── parsers.nim │ ├── random.nim │ ├── re.nim │ ├── say.nim │ ├── seqm.nim │ ├── setm.nim │ ├── std_jsonutils.nim │ ├── stringm.nim │ ├── support.nim │ ├── table.nim │ ├── terminal.nim │ ├── test.nim │ ├── time.nim │ └── tuplem.nim └── readme.md ├── ext ├── ext.nimble ├── ext │ ├── async.nim │ ├── cache.nim │ ├── cli_parser.nim │ ├── crawler.nim │ ├── csv.nim │ ├── grams.nim │ ├── gsl.nim │ ├── html.nim │ ├── http_client.nim │ ├── license │ ├── linalg.nim │ ├── memdb.nim │ ├── parser.nim │ ├── persistence.nim │ ├── ring_buffer.nim │ ├── search.nim │ ├── serie.nim │ ├── shell_call.nim │ ├── stats.nim │ ├── url.nim │ ├── vcache.nim │ ├── watch_dir.nim │ └── yaml.nim └── readme.md ├── ftext ├── ftext.nimble ├── ftext │ ├── old │ │ ├── bin │ │ │ ├── build_css │ │ │ └── serve_example_assets │ │ ├── render.nim │ │ ├── render │ │ │ ├── about-forex.ft │ │ │ ├── about-forex.html │ │ │ ├── about-forex │ │ │ │ └── knots │ │ │ │ │ ├── begushii.jpg │ │ │ │ │ ├── blakes-hitch.png │ │ │ │ │ ├── bulin.jpg │ │ │ │ │ ├── butterfly.jpg │ │ │ │ │ ├── constrictor-carabine.jpg │ │ │ │ │ ├── dvoynoy-bulin.jpg │ │ │ │ │ └── eight-slow.jpg │ │ │ ├── example.nim │ │ │ ├── ftext.css │ │ │ ├── reset.css │ │ │ ├── static_page.css │ │ │ ├── static_page_build.css │ │ │ └── tailwind.config.js │ │ └── render_test.nim │ ├── parse.nim │ ├── test.nim │ └── test │ │ ├── parse_test.nim │ │ └── some │ │ └── img.png ├── readme.md └── readme │ ├── keep.png │ ├── static_page.png │ └── vscode.png ├── keep ├── keep.nimble ├── keep │ ├── bin │ │ ├── build_css │ │ ├── copy_notes.nim │ │ ├── loc │ │ └── serve_assets │ ├── examples │ │ ├── notes │ │ │ ├── sample.ft │ │ │ └── sample │ │ │ │ └── knots │ │ │ │ ├── begushii.jpg │ │ │ │ ├── blakes-hitch.png │ │ │ │ ├── bulin.jpg │ │ │ │ ├── butterfly.jpg │ │ │ │ ├── constrictor-carabine.jpg │ │ │ │ ├── dvoynoy-bulin.jpg │ │ │ │ └── eight-slow.jpg │ │ └── run.nim │ ├── log.txt │ ├── main.nim │ ├── model.nim │ ├── model │ │ ├── configm.nim │ │ ├── dbm.nim │ │ ├── docm.nim │ │ ├── filter.nim │ │ ├── helpers.nim │ │ ├── load.nim │ │ └── schema.nim │ ├── render │ │ └── blocks.nim │ └── ui │ │ ├── assets │ │ ├── palette │ │ │ ├── build_palette.css │ │ │ ├── forest.html │ │ │ ├── ftext.css │ │ │ ├── icons │ │ │ │ ├── code.svg │ │ │ │ ├── controls.svg │ │ │ │ ├── cross.svg │ │ │ │ ├── down.svg │ │ │ │ ├── edit.svg │ │ │ │ ├── left.svg │ │ │ │ ├── link.svg │ │ │ │ ├── right.svg │ │ │ │ └── up.svg │ │ │ ├── palette.build.css │ │ │ ├── palette.css │ │ │ ├── palette.html │ │ │ ├── reset.css │ │ │ └── tailwind.config.js │ │ └── sample │ │ │ ├── forest.ft │ │ │ ├── forest │ │ │ ├── alton-tarp-guide.pdf │ │ │ ├── bow │ │ │ │ ├── buffallo-bull.jpg │ │ │ │ ├── indian.jpg │ │ │ │ ├── indian2.jpg │ │ │ │ ├── indian3.jpg │ │ │ │ └── indian4.jpg │ │ │ ├── knife │ │ │ │ ├── Toughness.png │ │ │ │ ├── friction-knife-2.jpg │ │ │ │ ├── friction-knife.jpg │ │ │ │ ├── noj.jpg │ │ │ │ └── steel.jpg │ │ │ ├── knots │ │ │ │ ├── adjustable-grip-hitch.jpg │ │ │ │ ├── begushii.jpg │ │ │ │ ├── blakes-hitch.png │ │ │ │ ├── bulin.jpg │ │ │ │ ├── butterfly.jpg │ │ │ │ ├── constrictor-carabine.jpg │ │ │ │ ├── dvoynoy-bulin.jpg │ │ │ │ ├── eight-slow.jpg │ │ │ │ ├── eight.jpg │ │ │ │ ├── kreplenie-palatki.png │ │ │ │ ├── losa.jpg │ │ │ │ ├── natyajenie.jpg │ │ │ │ ├── palomar.jpg │ │ │ │ ├── prqmoy.jpg │ │ │ │ ├── prusik.jpg │ │ │ │ ├── regulirovka-dlinni.jpg │ │ │ │ ├── shkotovyy.jpg │ │ │ │ ├── shtykovoy-so-shlagom.png │ │ │ │ ├── shtykovoy.jpg │ │ │ │ ├── shvativayushii.jpg │ │ │ │ ├── shwabish.jpg │ │ │ │ ├── stremya.jpg │ │ │ │ ├── travyanoy.jpg │ │ │ │ ├── uiaa.jpg │ │ │ │ ├── vosmerka.jpg │ │ │ │ └── vstrechny.jpg │ │ │ ├── misc │ │ │ │ ├── apache-wickiup.jpg │ │ │ │ ├── boat.jpg │ │ │ │ ├── clip.jpg │ │ │ │ ├── cutting.jpg │ │ │ │ ├── evenki.jpg │ │ │ │ ├── filter.jpg │ │ │ │ ├── finsky-sniper.jpg │ │ │ │ ├── fire.jpg │ │ │ │ ├── frame-of-apache-wickup.jpg │ │ │ │ ├── gamak-s-maskirovkoj.jpg │ │ │ │ ├── jivoj-zabor.gif │ │ │ │ ├── lovushka-dlya-ribi.jpg │ │ │ │ ├── malenkiy-koster-na-kamne.jpg │ │ │ │ ├── maskirovka-kamen.jpg │ │ │ │ ├── nabludenie.jpg │ │ │ │ ├── narti.jpg │ │ │ │ ├── obogrev.jpg │ │ │ │ ├── pech-kan.jpeg │ │ │ │ ├── plot.jpg │ │ │ │ ├── primitivnaya-obuv.jpg │ │ │ │ ├── rukzak-na-dereve.jpg │ │ │ │ ├── skolzyawij-uzel-dlya-kotelka.jpg │ │ │ │ ├── tent-types.jpg │ │ │ │ ├── tent.jpg │ │ │ │ ├── tiski.jpg │ │ │ │ ├── versha-1.jpg │ │ │ │ ├── versha-2.jpg │ │ │ │ ├── wigwam-kora.jpeg │ │ │ │ └── wooden-pin-for-cache-pit.gif │ │ │ ├── narty │ │ │ │ ├── 1.jpg │ │ │ │ ├── 10.jpg │ │ │ │ ├── 11.jpg │ │ │ │ ├── 12.jpg │ │ │ │ ├── 2.jpg │ │ │ │ ├── 3.jpg │ │ │ │ ├── 4.jpg │ │ │ │ ├── 5.jpg │ │ │ │ ├── 6.jpg │ │ │ │ ├── 7.jpg │ │ │ │ ├── 8.jpg │ │ │ │ └── 9.jpg │ │ │ ├── nsw-fire.pdf │ │ │ ├── radio.jpg │ │ │ ├── sofirn-hs05-1.jpg │ │ │ └── sofirn-hs05-2.jpg │ │ │ ├── sample.ft │ │ │ └── sample │ │ │ └── knots │ │ │ ├── begushii.jpg │ │ │ ├── blakes-hitch.png │ │ │ ├── bulin.jpg │ │ │ ├── butterfly.jpg │ │ │ ├── constrictor-carabine.jpg │ │ │ ├── dvoynoy-bulin.jpg │ │ │ ├── eight-slow.jpg │ │ │ ├── eight.jpg │ │ │ ├── kreplenie-palatki.png │ │ │ └── losa.jpg │ │ ├── helpers.nim │ │ ├── location.nim │ │ ├── pages │ │ ├── app_view.nim │ │ ├── doc_view.nim │ │ ├── filter_view.nim │ │ ├── search_view.nim │ │ └── warns_view.nim │ │ ├── palette.nim │ │ ├── partials │ │ ├── filter_tags.nim │ │ └── query_input.nim │ │ └── support.nim └── readme.md ├── mono ├── docs │ ├── how_it_works.md │ ├── inputs.md │ ├── keep-movies.png │ ├── keep1.png │ ├── old_readme.md │ ├── templates.md │ ├── thoughts.md │ ├── todo.png │ ├── twitter-no-frame copy.png │ ├── twitter-small.png │ └── twitter.png ├── mono.nimble ├── mono │ ├── bin │ │ ├── build_js │ │ └── loc │ ├── browser │ │ ├── helpers.js │ │ ├── helpers.ts │ │ ├── mono.css │ │ ├── mono.js │ │ ├── mono.ts │ │ └── tsconfig.json │ ├── core.nim │ ├── core │ │ ├── component.nim │ │ ├── diff.nim │ │ ├── macro_helpers.nim │ │ ├── mono_el.nim │ │ ├── sessionm.nim │ │ └── tmpl.nim │ ├── examples │ │ ├── explorer.nim │ │ ├── hello.nim │ │ ├── router.nim │ │ ├── todo.css │ │ ├── todo.nim │ │ ├── twitter.css │ │ ├── twitter.nim │ │ └── twitter_comp.nim │ ├── http.nim │ ├── http │ │ ├── helpers.nim │ │ ├── server.nim │ │ └── support.nim │ ├── log.txt │ ├── test.nim │ └── test │ │ ├── diff_test.nim │ │ ├── session_test.nim │ │ └── tmpl_test.nim └── readme.md ├── nodem ├── examples │ ├── math │ │ ├── math.nim │ │ ├── mathi.nim │ │ ├── user.nim │ │ └── user.sh │ ├── old_greeting │ │ ├── greeting.nim │ │ ├── greetingi.nim │ │ ├── user.nim │ │ └── useri.nim │ ├── redis │ │ ├── publisher.nim │ │ ├── redis.nim │ │ ├── redisi.nim │ │ └── user.nim │ ├── rest_api_in_5_lines │ │ └── server.nim │ └── rpc_in_10_lines │ │ ├── client.nim │ │ └── server.nim ├── license ├── nodem.nim ├── nodem.nimble ├── nodem │ ├── asyncm.nim │ ├── generatem.nim │ ├── httpm.nim │ ├── nexportm.nim │ ├── nimportm.nim │ ├── nodem.nim │ ├── parserm.nim │ ├── support_httpm.nim │ └── supportm.nim └── readme.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | tmp 3 | build 4 | play.nim 5 | plot/dev.env -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // "deno.enable": true, 3 | // "deno.unstable": true, 4 | // "deno.config": "./web/browser/tsconfig.json", 5 | // "deno.importMap": "./web/browser/import_map.json" 6 | } -------------------------------------------------------------------------------- /base/base.nim: -------------------------------------------------------------------------------- 1 | import std/sugar 2 | import ./base/[support, stringm, seqm, option, re, fallible, table, hash, tuplem, bitset, setm, enumm, 3 | log, json, env, say, math, random, time, basefs, test, parsers] 4 | 5 | import ./base/console_log 6 | 7 | export sugar 8 | export support, stringm, seqm, option, re, fallible, table, hash, tuplem, bitset, setm, enumm, 9 | log, json, env, say, math, random, time, basefs, test, parsers 10 | 11 | log_emitters.add emit_to_console -------------------------------------------------------------------------------- /base/base.nimble: -------------------------------------------------------------------------------- 1 | version = "3.5.1" 2 | author = "Alex Craft https://github.com/al6x" 3 | description = "Extensions for Nim std" 4 | license = "MIT" 5 | 6 | srcDir = "." 7 | 8 | requires "nim >= 1.6.12" 9 | -------------------------------------------------------------------------------- /base/base/bitset.nim: -------------------------------------------------------------------------------- 1 | import std/setutils 2 | 3 | export setutils 4 | 5 | proc to_bitset*[T](list: openarray[T]): set[T] = 6 | for v in list: result.incl v -------------------------------------------------------------------------------- /base/base/check.nim: -------------------------------------------------------------------------------- 1 | # Copied from std/unittest 2 | import std/macros 3 | 4 | macro check*(conditions: untyped): untyped = 5 | ## Verify if a statement or a list of statements is true. 6 | ## A helpful error message and set checkpoints are printed out on 7 | ## failure (if ``outputLevel`` is not ``PRINT_NONE``). 8 | runnableExamples: 9 | import std/strutils 10 | 11 | check("AKB48".toLowerAscii() == "akb48") 12 | 13 | let teams = {'A', 'K', 'B', '4', '8'} 14 | 15 | check: 16 | "AKB48".toLowerAscii() == "akb48" 17 | 'C' notin teams 18 | 19 | let checked = callsite()[1] 20 | 21 | template asgn(a: untyped, value: typed) = 22 | var a = value # XXX: we need "var: var" here in order to 23 | # preserve the semantics of var params 24 | 25 | template print(name: untyped, value: typed) = 26 | when compiles(string($value)): 27 | echo name & " was " & $value 28 | 29 | proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] = 30 | result.check = copyNimTree(exp) 31 | result.assigns = newNimNode(nnkStmtList) 32 | result.printOuts = newNimNode(nnkStmtList) 33 | 34 | var counter = 0 35 | 36 | if exp[0].kind in {nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym} and 37 | $exp[0] in ["not", "in", "notin", "==", "<=", 38 | ">=", "<", ">", "!=", "is", "isnot"]: 39 | 40 | for i in 1 ..< exp.len: 41 | if exp[i].kind notin nnkLiterals: 42 | inc counter 43 | let argStr = exp[i].toStrLit 44 | let paramAst = exp[i] 45 | if exp[i].kind == nnkIdent: 46 | result.printOuts.add getAst(print(argStr, paramAst)) 47 | if exp[i].kind in nnkCallKinds + {nnkDotExpr, nnkBracketExpr, nnkPar} and 48 | (exp[i].typeKind notin {ntyTypeDesc} or $exp[0] notin ["is", "isnot"]): 49 | let callVar = newIdentNode(":c" & $counter) 50 | result.assigns.add getAst(asgn(callVar, paramAst)) 51 | result.check[i] = callVar 52 | result.printOuts.add getAst(print(argStr, callVar)) 53 | if exp[i].kind == nnkExprEqExpr: 54 | # ExprEqExpr 55 | # Ident "v" 56 | # IntLit 2 57 | result.check[i] = exp[i][1] 58 | if exp[i].typeKind notin {ntyTypeDesc}: 59 | let arg = newIdentNode(":p" & $counter) 60 | result.assigns.add getAst(asgn(arg, paramAst)) 61 | result.printOuts.add getAst(print(argStr, arg)) 62 | if exp[i].kind != nnkExprEqExpr: 63 | result.check[i] = arg 64 | else: 65 | result.check[i][1] = arg 66 | 67 | case checked.kind 68 | of nnkCallKinds: 69 | 70 | let (assigns, check, printOuts) = inspectArgs(checked) 71 | let lineinfo = newStrLitNode(checked.lineInfo) 72 | let callLit = checked.toStrLit 73 | result = quote do: 74 | block: 75 | `assigns` 76 | if not `check`: 77 | echo "\e[31m" & "Failed: " & `callLit` & "\e[0m" & " " & "\e[90m" & `lineinfo` & "\e[0m" 78 | `printOuts` 79 | quit() 80 | 81 | of nnkStmtList: 82 | result = newNimNode(nnkStmtList) 83 | for node in checked: 84 | if node.kind != nnkCommentStmt: 85 | result.add(newCall(newIdentNode("check"), node)) 86 | 87 | else: 88 | let lineinfo = newStrLitNode(checked.lineInfo) 89 | let callLit = checked.toStrLit 90 | 91 | result = quote do: 92 | if not `checked`: 93 | echo "\e[31m" & "Failed: " & `callLit` & "\e[0m" & " " & "\e[90m" & `lineinfo` & "\e[0m" 94 | quit() 95 | -------------------------------------------------------------------------------- /base/base/doc.nim: -------------------------------------------------------------------------------- 1 | import sequtils, strutils, strformat 2 | 3 | type 4 | TodoPriority* = enum high, normal, low 5 | 6 | DocKind* = enum text, todo 7 | 8 | DocItem* = object 9 | tags: seq[string] 10 | text: string 11 | 12 | case kind: DocKind 13 | of text: 14 | title: string 15 | of todo: 16 | priority: TodoPriority 17 | 18 | # converter to_doc_level*(s: string): TodoPriority = parse_enum[TodoPriority](fmt"{s}_e") 19 | 20 | var docs*: seq[DocItem] 21 | 22 | proc doc*(title: string, text: string, tags: openarray[string] = []): void = 23 | docs.add DocItem(kind: DocKind.text, title: title, text: text, tags: tags.to_seq) 24 | 25 | proc todo*(text: string, priority: TodoPriority = normal, tags: openarray[string] = []): void = 26 | docs.add DocItem(kind: todo, text: text, priority: priority, tags: tags.to_seq) -------------------------------------------------------------------------------- /base/base/enumm.nim: -------------------------------------------------------------------------------- 1 | import std/[macros, strutils] 2 | 3 | macro autoconvert*(TT: type[enum]) = 4 | let fname = ident "to_" & $(TT) 5 | quote do: 6 | converter `fname`*(s: string): `TT` = parse_enum[`TT`](s) -------------------------------------------------------------------------------- /base/base/env.nim: -------------------------------------------------------------------------------- 1 | import std/[strutils, os, tables] 2 | 3 | # Checking required language features -------------------------------------------------------------- 4 | template try_define_overloadable_enums = # Checking if `--experimental:overloadable_enums` enabled 5 | block: 6 | type EA = enum e1 7 | type EB = enum e1 8 | 9 | when not compiles(try_define_overloadable_enums()): 10 | # when not defined(nim_has_overloadable_enums): 11 | static: 12 | echo "Error: nim experimental feature required, run nim with `--experimental:overloadable_enums` flag." 13 | quit(1) 14 | 15 | # Env ---------------------------------------------------------------------------------------------- 16 | type Env* = Table[string, string] 17 | 18 | let env* = block: 19 | var env = Env() 20 | for (k, v) in env_pairs(): env[k] = v 21 | 22 | # Adding arguments from command line 23 | for i in (1..param_count()): 24 | let token = param_str(i) 25 | if not token.starts_with("\""): 26 | if "=" in token: 27 | let pair = token.split("=", 2) 28 | # Keys are normalized, lowercased with replaced `_` 29 | env[pair[0]] = pair[1] 30 | else: 31 | # Non key/value argument with name matching `T.key` treating as boolean key/value 32 | env[token] = "true" 33 | env 34 | 35 | proc `[]`*(env: Env, key: string, default: string): string = 36 | if key in env: env[key] else: default 37 | 38 | # Test --------------------------------------------------------------------------------------------- 39 | # nim c -r base/base/env.nim test 40 | if is_main_module: 41 | echo env["test", "false"] 42 | 43 | 44 | # const special_arguments = { "help": "bool" }.to_table 45 | 46 | # Normalizing keys, so both camel case and underscore keys will be matched 47 | # func normalize_key(v: string): string = v.to_lower.replace("_", "") 48 | 49 | # contains ----------------------------------------------------------------------------------------- 50 | # proc contains*(env: Env, key: string): bool = 51 | # key.normalize_key in env.values 52 | 53 | # [], get_optional --------------------------------------------------------------------------------- 54 | # proc get_optional*(env: Env, key: string): Option[string] = 55 | # let normalized = key.normalize_key 56 | # if normalized in env.values: env.values[key.normalize_key].some else: string.none 57 | 58 | # proc `[]`*(env: Env, key: string): string = 59 | # let normalized = key.normalize_key 60 | # if normalized in env.values: env.values[key.normalize_key] else: throw fmt"no environment variable '{key}'" 61 | 62 | # environment mode --------------------------------------------------------------------------------- 63 | # let env_mode = env["env", "dev"] 64 | # if env_mode notin ["dev", "test", "prod"]: throw fmt"invalid env mode {env_mode}" 65 | # proc is_prod*(env: Env): bool = env_mode == "prod" 66 | # proc is_test*(env: Env): bool = env_mode == "test" 67 | # proc is_dev*(env: Env): bool = env_mode == "dev" 68 | -------------------------------------------------------------------------------- /base/base/fallible.nim: -------------------------------------------------------------------------------- 1 | import std/sugar 2 | import ./support, ./option, ./table, ./json, ./test 3 | 4 | type Fallible*[T] = object 5 | case is_error*: bool 6 | of true: 7 | message*: string 8 | of false: 9 | value*: T 10 | 11 | 12 | func is_success*[T](e: Fallible[T]): bool = not e.is_error 13 | func is_error*[T](e: Fallible[T]): bool = e.is_error 14 | 15 | func is_empty*[T](e: Fallible[T]): bool {.inline.} = e.is_error 16 | func is_blank*[T](e: Fallible[T]): bool {.inline.} = e.is_error 17 | func is_present*[T](e: Fallible[T]): bool {.inline.} = not e.is_error 18 | 19 | 20 | func get*[T](e: Fallible[T]): T = 21 | if e.is_error: throw(e.message) else: e.value 22 | 23 | 24 | func error*(T: type, message: string): Fallible[T] = Fallible[T](is_error: true, message: message) 25 | 26 | 27 | func success*[T](value: T): Fallible[T] = Fallible[T](is_error: false, value: value) 28 | 29 | func success*[T](value: Option[T]): Fallible[T] = Fallible[T](is_error: false, value: value.get) 30 | 31 | # func set*[T](value: T): Fallible[T] = Fallible[T](is_error: false, value: value) 32 | 33 | # func set*[T](value: Option[T]): Fallible[T] = Fallible[T](is_error: false, value: value.get) 34 | 35 | 36 | # Used in `a.to(Fallible[B])` conversions 37 | proc init*[B, A](_: type[Fallible[B]], a: Fallible[A]): Fallible[B] = 38 | assert a.is_error, "init possible for errors only" 39 | B.error(a.message) 40 | 41 | test "init": 42 | discard Fallible[string].init(int.error("some error")) 43 | discard int.error("some error").to(Fallible[float]) 44 | 45 | 46 | func errors*[T](list: seq[Fallible[T]]): seq[string] = 47 | list.filter_map(proc (e: Fallible[T]): auto = 48 | if e.is_error: e.message.some else: string.none 49 | ) 50 | 51 | func errors*[K, V](table: Table[K, Fallible[V]] | ref Table[K, Fallible[V]]): Table[K, string] = 52 | table.filter_map(proc (e: Fallible[V]): auto = 53 | if e.is_error: e.message.some else: string.none 54 | ) 55 | 56 | 57 | func successes*[T](list: seq[Fallible[T]]): seq[T] = 58 | for v in list: 59 | if v.is_present: 60 | result.add v.get 61 | 62 | func successes*[K, V](table: Table[K, Fallible[V]] | ref Table[K, Fallible[V]]): Table[K, V] = 63 | table.filter_map(proc (e: Fallible[V]): auto = 64 | if e.is_error: V.none else: e.value.some 65 | ) 66 | 67 | 68 | proc map*[A, B](a: Fallible[A], op: (A) -> B): Fallible[B] = 69 | if a.is_success: op(a.get).success else: B.error(a.message) 70 | 71 | 72 | converter to_option*[T](v: Fallible[T]): Option[T] = 73 | if v.is_present: v.get.some else: T.none 74 | 75 | # proc `%`*[T](f: Fallible[T]): JsonNode = 76 | # if f.is_error: %f else: %(f.get) 77 | 78 | proc from_json_hook*[T](v: var Fallible[T], json: JsonNode) = 79 | v = if json.kind == JObject and "is_error" in json: 80 | if json["is_error"].get_bool: 81 | let message = 82 | if "error" in json: json["error"].get_str 83 | elif "message" in json: json["message"].get_str 84 | else: "unknown error" 85 | T.error message 86 | else: 87 | json["value"].json_to(T).success 88 | else: 89 | json.json_to(T).success 90 | 91 | proc to_json_hook*[T](v: Fallible[T]): JsonNode = 92 | if v.is_error: (is_error: true, message: v.message).to_json 93 | else: v.get.to_json -------------------------------------------------------------------------------- /base/base/hash.nim: -------------------------------------------------------------------------------- 1 | import std/hashes 2 | 3 | export hashes 4 | 5 | 6 | # autohash ----------------------------------------------------------------------------------------- 7 | proc autohash*[T: tuple|object](o: T): Hash = 8 | for f in o.fields: result = result !& f.hash 9 | result = !$result 10 | 11 | proc autohash*[T: ref object](o: T): Hash = 12 | for f in o[].fields: result = result !& f.hash 13 | result = !$result 14 | -------------------------------------------------------------------------------- /base/base/license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Petrushin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /base/base/option.nim: -------------------------------------------------------------------------------- 1 | import std/[options, sugar] 2 | # import ./support, 3 | 4 | export options except option 5 | 6 | template throw(message: string) = raise Exception.new_exception(message) 7 | 8 | func ensure*[T](o: Option[T], message: string): T = 9 | if o.is_none: throw(message) else: o.get 10 | 11 | func ensure*[T](o: Option[T], message: () -> string): T = 12 | if o.is_none: throw(message()) else: o.get 13 | 14 | 15 | template applyit*[T](o: Option[T], expr: untyped) = 16 | if o.is_some: 17 | let it {.inject.} = o.get 18 | expr 19 | 20 | func is_empty*[T](o: Option[T]): bool {.inline.} = o.is_none 21 | func is_blank*[T](o: Option[T]): bool {.inline.} = o.is_none 22 | func is_present*[T](o: Option[T]): bool {.inline.} = o.is_some 23 | 24 | # proc set*[T](v: T): Option[T] {.inline.} = v.some 25 | 26 | proc getset*[T](o: var Option[T], v: T): T = 27 | if o.is_none: 28 | o = v.some 29 | o.get 30 | 31 | proc get*[T](o: Option[T], otherwise: () -> T): T = 32 | if o.is_some: o.get else: otherwise() 33 | 34 | proc clear*[T](o: var Option[T]): void = 35 | o = T.none 36 | 37 | proc `==`*[T](a: Option[T], b: T): bool = 38 | a.is_some and a.get == b 39 | proc `==`*[T](a: T, b: Option[T]): bool = 40 | b.is_some and b.get == a 41 | 42 | proc contains*[T](s: seq[T] | set[T], v: Option[T]): bool = 43 | v.is_some and s.contains(v.get) 44 | 45 | iterator items*[T](o: Option[T]): T = 46 | if o.is_some: yield o.get 47 | 48 | when is_main_module: 49 | assert "a".some == "a" and "a" == "a".some 50 | assert 'a'.some in {'a'} and 'a' in @['a'] 51 | assert (case 'a' 52 | of {'a'}: 1 53 | else: 2 54 | ) == 1 -------------------------------------------------------------------------------- /base/base/parsers.nim: -------------------------------------------------------------------------------- 1 | import std/[strformat, options] 2 | 3 | 4 | # T.field_names, o.field_names --------------------------------------------------------------------- 5 | proc field_names*[T](o: T): seq[string] = 6 | for k, _ in o.field_pairs: result.add k 7 | result.sort 8 | 9 | # proc field_names*[T](o: ref T): seq[string] = 10 | # o[].field_names 11 | 12 | # template field_names*[T](_: type[T]): seq[string] = 13 | # var t: T # won't work for variant, as the default variant will be created 14 | # var names: seq[string] 15 | # when t is ref object: 16 | # for k, _ in t[].field_pairs: names.add k 17 | # else: 18 | # for k, _ in t.field_pairs: names.add k 19 | # names 20 | 21 | 22 | # to(bool) ----------------------------------------------------------------------------------------- 23 | # proc to*(v: string, _: type[bool]): bool = 24 | # case v.to_lower 25 | # of "yes", "true", "t": true 26 | # of "no", "false", "f": false 27 | # else: raise Exception.new_exception(fmt"invalid bool '{v}'") 28 | 29 | # proc to*(v: string, _: type[bool], default: bool): bool = 30 | # if v == "": default else: v.to(bool) 31 | 32 | 33 | # parse -------------------------------------------------------------------------------------------- 34 | proc serialize*(v: int): string = 35 | $v 36 | proc parse*(_: type[int], v: string): int = 37 | v.parse_int 38 | 39 | proc serialize*(v: float): string = 40 | $v 41 | proc parse*(_: type[float], v: string): float = 42 | v.parse_float 43 | 44 | proc serialize*(v: string): string = 45 | $v 46 | proc parse*(_: type[string], v: string): string = 47 | v 48 | 49 | proc serialize*(v: bool): string = 50 | if v: "true" else: "false" 51 | proc parse*(_: type[bool], v: string): bool = 52 | if v == "true": true 53 | elif v == "false": false 54 | else: v.to_lower in ["true", "t", "yes", "y", "on", "1"] 55 | 56 | proc serialize*[T](v: Option[T]): string = 57 | if v.is_some: v.get.serialize else: "" 58 | proc parse*[T](_: type[Option[T]], v: string): Option[T] = 59 | if v == "": T.none else: T.parse(v).some -------------------------------------------------------------------------------- /base/base/random.nim: -------------------------------------------------------------------------------- 1 | import std/[times, hashes, random, strutils] 2 | import ./env as envm, ./table 3 | 4 | export Rand 5 | 6 | proc init(tself: type[Rand], seed = 1): Rand = 7 | init_rand(seed) 8 | 9 | var default_rgen* = Rand.init 10 | 11 | proc rand*[T: Ordinal or SomeFloat](range: HSlice[T, T], rgen: var Rand = default_rgen): T = 12 | random.rand(rgen, range) 13 | 14 | proc rand*(max: Natural, rgen: var Rand = default_rgen): int = 15 | random.rand(rgen, max) 16 | 17 | proc rand*(max: float, rgen: var Rand = default_rgen): float = 18 | random.rand(rgen, max) 19 | 20 | proc rand*(_: type[bool], rgen: var Rand = default_rgen): bool = 21 | random.rand(rgen, 1) > 0 22 | 23 | proc rand*[V](list: openarray[V], rgen: var Rand = default_rgen): V = 24 | list[(list.len - 1).rand(rgen)] 25 | 26 | proc sample*[V](list: openarray[V], count: int, rgen: var Rand = default_rgen): seq[V] = 27 | for _ in 1..count: 28 | result.add list.rand(rgen) 29 | 30 | 31 | # secure_rgen -------------------------------------------------------------------------------------- 32 | let seed1: int64 = block: 33 | let now = get_time() 34 | var rgen = init_rand(now.to_unix * 1_000_000_000 + now.nanosecond) 35 | @[rgen.rand(int.high), env.values.hash.int].hash.int64 36 | 37 | proc secure_rgen*(): Rand = 38 | let now = get_time() 39 | let seed2 = now.to_unix * 1_000_000_000 + now.nanosecond 40 | init_rand(@[seed1, seed2].hash) 41 | 42 | # secure_random_token ------------------------------------------------------------------------------ 43 | let az = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" 44 | let azAz09 = (az & " " & az.to_lower & " 0 1 2 3 4 5 6 7 8 9").split(" ") 45 | 46 | proc secure_random_token*(n = 30): string = 47 | var rgen = secure_rgen() 48 | # $secure_hash(rgen.rand(int.high).to_s) 49 | azAz09.sample(n, rgen).join("") 50 | 51 | 52 | # Test --------------------------------------------------------------------------------------------- 53 | if is_main_module: 54 | echo secure_random_token() 55 | -------------------------------------------------------------------------------- /base/base/say.nim: -------------------------------------------------------------------------------- 1 | import osproc 2 | 3 | proc say*(text: string): void = 4 | try: 5 | discard exec_process("say \"" & text & "\"") 6 | except: 7 | discard -------------------------------------------------------------------------------- /base/base/setm.nim: -------------------------------------------------------------------------------- 1 | import std/sets 2 | 3 | export sets 4 | 5 | # is_empty ----------------------------------------------------------------------------------------- 6 | func is_empty*[T](s: HashSet[T]): bool = s.len == 0 7 | func is_blank*[T](s: HashSet[T]): bool = s.len == 0 8 | 9 | func add*[T](s: var HashSet[T], v: T) = 10 | s.incl v -------------------------------------------------------------------------------- /base/base/terminal.nim: -------------------------------------------------------------------------------- 1 | proc green*(s: string): string = "\e[32m" & s & "\e[0m" 2 | proc grey*(s: string): string = "\e[90m" & s & "\e[0m" 3 | proc yellow*(s: string): string = "\e[33m" & s & "\e[0m" 4 | proc red*(s: string): string = "\e[31m" & s & "\e[0m" -------------------------------------------------------------------------------- /base/base/test.nim: -------------------------------------------------------------------------------- 1 | import std/[strutils] 2 | import ./env as envm, ./check 3 | from ./terminal as terminal import nil 4 | 5 | export check 6 | export strutils.align_left # avoiding exporting all as it contains `%` which overrides json.% 7 | 8 | template print_test_info(slow: bool, filename, testname: string, failed: bool): void = 9 | const id_len = 7 10 | let fname = filename.split(".")[0] 11 | let ffname = fname.replace("_test", "").align_left(id_len)[0..(id_len-1)] 12 | let module = if slow: "stest" else: " test" 13 | if failed: 14 | echo terminal.red(" " & module & " | " & ffname & " " & testname & " failed") 15 | else: 16 | echo terminal.grey(" " & module & " | " & ffname & " " & testname) 17 | 18 | template test*(name: string, body) = 19 | let test_variable = env["test", "false"] 20 | if test_variable == "true" or test_variable == "fast" or test_variable == name: 21 | let pos = instantiation_info() 22 | print_test_info(false, pos.filename, name, false) 23 | # try: 24 | body 25 | # except Exception as e: 26 | # print_test_info(false, pos.filename, name, true) 27 | # quit e.msg 28 | 29 | template slow_test*(name: string, body) = 30 | let test_variable = env["test", "false"] 31 | if test_variable == "true" or test_variable == name: 32 | let pos = instantiation_info() 33 | print_test_info(true, pos.filename, name, false) 34 | # try: 35 | body 36 | # except Exception as e: 37 | # print_test_info(true, pos.filename, name, true) 38 | # quit e.msg 39 | 40 | if is_main_module: 41 | test "some": 42 | echo "testing" -------------------------------------------------------------------------------- /base/base/tuplem.nim: -------------------------------------------------------------------------------- 1 | import std/[sugar, macros] 2 | import ./test 3 | 4 | template throw(message: string) = raise Exception.new_exception(message) 5 | 6 | proc is_empty*(o: tuple): bool = 7 | for _, _ in o.field_pairs: return false 8 | return true 9 | 10 | 11 | func to_tuple1*[T](list: openarray[T]): (T,) = 12 | if list.len != 1: throw fmt"expected 1 elements but found {list.len}" 13 | (list[0],) 14 | 15 | func to_tuple2*[T](list: openarray[T]): (T, T) = 16 | if list.len != 2: throw fmt"expected 2 elements but found {list.len}" 17 | (list[0], list[1]) 18 | 19 | func to_tuple3*[T](list: openarray[T]): (T, T, T) = 20 | if list.len != 3: throw fmt"expected 3 elements but found {list.len}" 21 | (list[0], list[1], list[2]) 22 | 23 | func to_tuple4*[T](list: openarray[T]): (T, T, T, T) = 24 | if list.len != 4: throw fmt"expected 4 elements but found {list.len}" 25 | (list[0], list[1], list[2], list[4]) 26 | 27 | func to_tuple5*[T](list: openarray[T]): (T, T, T, T, T) = 28 | if list.len != 5: throw fmt"expected 5 elements but found {list.len}" 29 | (list[0], list[1], list[2], list[4], list[5]) 30 | 31 | 32 | # map ---------------------------------------------------------------------------------------------- 33 | func map*[V, R](t: (V, ), op: (v: V) -> R): (R, ) = 34 | (op(t[0]), ) 35 | 36 | func map*[V, R](t: (V, V), op: (v: V) -> R): (R, R) = 37 | (op(t[0]), op(t[1])) 38 | 39 | func map*[V, R](t: (V, V, V), op: (v: V) -> R): (R, R, R) = 40 | (op(t[0]), op(t[1]), op(t[2])) 41 | 42 | 43 | # template set_from_tuple*(obj: ref object, attrs: tuple) = 44 | # for tk, tv in field_pairs(attrs): 45 | # block field_found: 46 | # for ok, ov in field_pairs(obj[]): 47 | # when ok == tk: 48 | # ov = tv 49 | # break field_found 50 | 51 | # template set_from_tuple*(obj: ref object, attrs: tuple) = 52 | # set_from_tuple obj[], attrs 53 | 54 | # test "set_from_tuple": 55 | # type Button = object 56 | # color: string 57 | # another: int 58 | # var b = Button() 59 | # b.set_from_tuple (color: "red") 60 | # check b.color == "red" 61 | 62 | # func map*[V, R](t: (V, V, V, V), op: (v: V) -> R): (R, R, R, R) = 63 | # (op(t[0]), op(t[1]), op(t[2]), op(t[3])) 64 | 65 | # func map*[V, R](t: (V, V, V, V, V), op: (v: V) -> R): (R, R, R, R, R) = 66 | # (op(t[0]), op(t[1]), op(t[2]), op(t[3]), op(t[4])) 67 | 68 | # # first -------------------------------------------------------------------------------------------- 69 | # func first*[V](v: (V,)): V = v[0] 70 | # func first*[V, B](v: (V, B)): V = v[0] 71 | # func first*[V, B, C](v: (V, B, C)): V = v[0] 72 | # func first*[V, B, C, D](v: (V, B, C, D)): V = v[0] 73 | 74 | # # first -------------------------------------------------------------------------------------------- 75 | # func second*[V, B](v: (V, B)): B = v[1] 76 | # func second*[V, B, C](v: (V, B, C)): B = v[1] 77 | # func second*[V, B, C, D](v: (V, B, C, D)): B = v[1] 78 | 79 | # # third -------------------------------------------------------------------------------------------- 80 | # func third*[V, B, C](v: (V, B, C)): C = v[2] 81 | # func third*[V, B, C, D](v: (V, B, C, D)): C = v[2] 82 | 83 | # # last --------------------------------------------------------------------------------------------- 84 | # func last*[V, B](v: (V, B)): B = v[1] 85 | # func last*[V, B, C](v: (V, B, C)): C = v[2] 86 | # func last*[V, B, C, D](v: (V, B, C, D)): D = v[3] 87 | -------------------------------------------------------------------------------- /base/readme.md: -------------------------------------------------------------------------------- 1 | Extensions for Nim standard library. 2 | 3 | - Fixing outdated stuff like old functions written with `var arg` style. 4 | - Fixing conflicts with iterators like `table.keys`. 5 | - Replacing old and badly written modules like `std/re`. 6 | - And some addons like `seq.is_empty`. 7 | 8 | Made by [al6x](http://al6x.com). 9 | 10 | # License 11 | 12 | MIT -------------------------------------------------------------------------------- /ext/ext.nimble: -------------------------------------------------------------------------------- 1 | version = "3.5.1" 2 | author = "Alex Craft https://github.com/al6x" 3 | description = "Extensions for Nim std" 4 | license = "MIT" 5 | 6 | srcDir = "." 7 | 8 | requires "nim >= 1.6.12" 9 | requires "yaml == 1.1.0" 10 | requires "https://github.com/al6x/nim?subdir=base == 3.5.1" 11 | -------------------------------------------------------------------------------- /ext/ext/async.nim: -------------------------------------------------------------------------------- 1 | import base, std/asyncdispatch 2 | 3 | export asyncdispatch except async_check, with_timeout, add_timer 4 | 5 | # Optional helpers for async, you don't have to use it 6 | 7 | # spawn_async -------------------------------------------------------------------------------------- 8 | proc ignore_exceptions*[T](future: Future[T]): Future[T] {.async.} = 9 | try: await future 10 | except: discard 11 | 12 | proc ignore_exceptions*(future: Future[void]): Future[void] {.async.} = 13 | try: await future 14 | except: discard 15 | 16 | proc spawn_async*[T](future: Future[T], check = true) = 17 | if check: asyncdispatch.async_check future 18 | else: asyncdispatch.async_check future.ignore_exceptions 19 | 20 | proc spawn_async*[T](afn: proc: Future[T], check = true) = 21 | proc on_next_tick {.gcsafe.} = 22 | if check: asyncdispatch.async_check afn() 23 | else: 24 | try: 25 | asyncdispatch.async_check afn().ignore_exceptions 26 | except: 27 | discard 28 | asyncdispatch.call_soon on_next_tick 29 | 30 | proc spawn_async*(fn: proc: void, check = true) = 31 | spawn_async((proc: Future[void] {.async.} = fn()), check) 32 | 33 | 34 | # timeout ------------------------------------------------------------------------------------------ 35 | proc with_timeout*[T](future: Future[T], timeout_ms: int, message = "timed out"): Future[T] {.async.} = 36 | if await asyncdispatch.with_timeout(future, timeout_ms): 37 | return await future 38 | else: 39 | throw message 40 | 41 | proc with_timeout*(future: Future[void], timeout_ms: int, message = "timed out"): Future[void] {.async.} = 42 | if await asyncdispatch.with_timeout(future, timeout_ms): 43 | return 44 | else: 45 | raise new_exception(Exception, message) 46 | 47 | 48 | # spawn_async -------------------------------------------------------------------------------------- 49 | proc clean_async_error*(error: string): string = 50 | error.replace(re"\nAsync traceback:[\s\S]+", "") 51 | 52 | 53 | # add_timer ---------------------------------------------------------------------------------------- 54 | proc add_timer*(timeout_ms: int, cb: proc: void, once = false, immediatelly = false) = 55 | proc timer_wrapper(_: AsyncFD): bool {.gcsafe.} = 56 | cb() 57 | once 58 | if immediatelly: cb() 59 | asyncdispatch.add_timer(timeout_ms, once, timer_wrapper) 60 | 61 | 62 | # build_sync_timer --------------------------------------------------------------------------------- 63 | proc build_sync_timer*(timeout_ms: int, cb: proc: void, once = false, immediatelly = false): proc() = 64 | if immediatelly: 65 | cb() 66 | if once: 67 | return proc = discard 68 | 69 | var tic_ms = timer_ms(); var active = true 70 | proc = 71 | if active and tic_ms() > timeout_ms: 72 | tic_ms = timer_ms() 73 | cb() 74 | if once: active = false 75 | 76 | # test --------------------------------------------------------------------------------------------- 77 | when is_main_module: 78 | let st = build_sync_timer(100, () => p 1) 79 | let tic = timer_ms() 80 | while tic() < 1000: 81 | st() -------------------------------------------------------------------------------- /ext/ext/cli_parser.nim: -------------------------------------------------------------------------------- 1 | # parse_env_variable ------------------------------------------------------------------------------- 2 | 3 | 4 | 5 | # parse_env ---------------------------------------------------------------------------------------- 6 | proc parse_env*[T: tuple|object]( 7 | _: type[T], 8 | default: T, 9 | required_args = (0, 0), 10 | required_options: openarray[string] = [], 11 | env = env 12 | ): (T, seq[string]) = # (options, args) 13 | var o = default; var help = false 14 | 15 | # List of keys in object 16 | var object_keys, normalized_object_keys: seq[string] 17 | for k, v in o.field_pairs: 18 | object_keys.add k 19 | normalized_object_keys.add k.normalize_key 20 | 21 | # Parsing command line arguments 22 | var options: Table[string, string] 23 | var args: seq[string] 24 | for i in (1..param_count()): 25 | let token = param_str(i) 26 | if token.starts_with("\""): 27 | args.add token 28 | else: 29 | if "=" in token: 30 | let pair = token.split("=", 2) 31 | # Keys are normalized, lowercased with replaced `_` 32 | options[pair[0].normalize_key] = pair[1] 33 | # elif token.normalize_key in normalized_object_keys or token.normalize_key in special_arguments: 34 | elif token.normalize_key in normalized_object_keys: 35 | # Non key/value argument with name matching `T.key` treating as boolean key/value 36 | options[token.normalize_key] = "true" 37 | elif token.normalize_key == "help": 38 | help = true 39 | else: 40 | args.add token 41 | 42 | block: # Printing help 43 | if help: 44 | if required_args[1] > 0: 45 | if required_args[1] == required_args[0]: 46 | echo fmt"arguments count {required_args[0]}" 47 | else: 48 | echo fmt"arguments count {required_args[0]}..{required_args[1]}" 49 | 50 | for k, v in o.field_pairs: 51 | echo " - " & k & ": " & $(typeof v) & (if k in required_options: ", required" else: "") 52 | echo "" 53 | # for k, v in special_arguments: 54 | # echo terminal.grey(fmt" - {k}, {v}") 55 | echo terminal.grey(fmt" - help: bool") 56 | 57 | quit(0) 58 | 59 | block: # Validating args 60 | if required_args[1] == 0 and args.len > 0: 61 | throw fmt"Expected no arguments, but got {args.len}" 62 | if args.len < required_args[0] or args.len > required_args[1]: 63 | throw fmt"Expected {required_args[0]}..{required_args[1]} arguments, but got {args.len}" 64 | for k, _ in options: 65 | # if k notin normalized_object_keys and k notin special_arguments: 66 | if k notin normalized_object_keys and k != "help": 67 | throw fmt"Unknown option '{k}'" 68 | 69 | func ktype(k: string): string = 70 | for k2, v in o.field_pairs: 71 | if k == k2: return $(typeof v) 72 | throw fmt"Invalid '{k}'" 73 | for k in required_options: 74 | if k.normalize_key notin options: 75 | throw fmt"Option '{k}', {ktype(k)}, required" 76 | 77 | # Parsing and casting into object 78 | for k, v in o.field_pairs: 79 | let nk = k.normalize_key 80 | if nk in options: v = typeof(v).parse options[nk] 81 | elif nk in env.values: v = typeof(v).parse env.values[nk] 82 | 83 | (o, args) 84 | 85 | 86 | # Test --------------------------------------------------------------------------------------------- 87 | # nim c -r base/base/env.nim file=a.txt some_flag lines=2 something 88 | if is_main_module: 89 | type Config = object 90 | file: string 91 | lines: int 92 | some_flag: bool 93 | 94 | echo Config.parse_env( 95 | default = Config(file: "", lines: 2, some_flag: false), 96 | required_options = ["file"], 97 | required_args = (1, 2) 98 | ) -------------------------------------------------------------------------------- /ext/ext/csv.nim: -------------------------------------------------------------------------------- 1 | import std/[parsecsv, strutils, sugar, strformat] 2 | import ./support, ./option, ./table 3 | 4 | # map_csv ------------------------------------------------------------------------------------------ 5 | proc map_csv*[T]( 6 | csv_file_path: string, 7 | map: proc( 8 | row: proc(key: string): string 9 | ): T, 10 | separator = ',' 11 | ): seq[T] = 12 | var parser: CsvParser 13 | 14 | try: 15 | parser.open(csv_file_path, separator = separator) 16 | except: 17 | # closing parser would trigger the null pointer exception and it won't be caught by except 18 | # try: parser.close except: discard 19 | throw fmt"can't read CSV file, {get_current_exception().message}" 20 | 21 | try: 22 | parser.read_header_row 23 | let column_indexes = parser.headers.to_index 24 | while parser.read_row: 25 | proc row(key: string): string = 26 | let index = column_indexes.ensure(key, fmt"unknown CSV row '{key}'") 27 | if index >= parser.row.len: 28 | # Handling bug in `parsecsv`, it parses "a,b," as 2 length seq, but it should be 3 29 | return "" 30 | else: 31 | parser.row[index] 32 | result.add map row 33 | finally: 34 | parser.close 35 | result 36 | 37 | 38 | # to_csv ------------------------------------------------------------------------------------------- 39 | proc to_csv( 40 | rows: seq[seq[string]], 41 | header: Option[seq[string]] = seq[string].none, 42 | delimiter: string 43 | ): string = 44 | var buff: seq[string] 45 | var ncolumns = if header.is_some: header.get.len 46 | else: 47 | assert rows.len > 0, "can't write empty CSV" 48 | rows[0].len 49 | 50 | if header.is_some: buff.add header.get.join(delimiter) 51 | for row in rows: buff.add row.join(delimiter) 52 | buff.join("\n") 53 | 54 | proc to_csv*( 55 | rows: seq[seq[string]], 56 | delimiter = "," 57 | ): string = to_csv(rows, seq[string].none, delimiter) 58 | 59 | proc to_csv*( 60 | rows: seq[seq[string]], 61 | header: seq[string], 62 | delimiter = "," 63 | ): string = to_csv(rows, header.some, delimiter) 64 | 65 | test "to_csv": 66 | let header = @["name", "value"] 67 | let rows = @[ 68 | @["a", "1"], 69 | @["b", "2"] 70 | ] 71 | assert rows.to_csv(header) == "name,value\n" & "a,1\n" & "b,2" -------------------------------------------------------------------------------- /ext/ext/grams.nim: -------------------------------------------------------------------------------- 1 | import base 2 | 3 | # Bigrams ----------------------------------------------------------------------------------------- 4 | type Bigram = array[2, char] 5 | 6 | proc init*(_: type[Bigram], s: string, i: int): Bigram {.inline.} = 7 | [s[i], s[i+1]] 8 | 9 | proc `==`(a, b: Bigram): bool {.inline.} = 10 | a[0] == b[0] and a[1] == b[1] 11 | 12 | proc hash(t: Bigram): Hash {.inline.} = 13 | !$(t[0].hash !& t[1].hash) 14 | 15 | proc `$`(t: Bigram): string = 16 | t[0] & t[1] 17 | 18 | var bigram_codes: Table[Bigram, int] 19 | template encode_bigram*(s: Bigram): int = 20 | bigram_codes.mget_or_put(s, bigram_codes.len) 21 | 22 | var bigram_rcodes: Table[int, Bigram] 23 | proc rebuild_bigram_rcodes = 24 | for g, c in bigram_codes: 25 | if c notin bigram_rcodes: bigram_rcodes[c] = g 26 | 27 | proc decode_bigram*(code: int): Bigram = 28 | if code notin bigram_rcodes: rebuild_bigram_rcodes() 29 | bigram_rcodes[code] 30 | 31 | proc to_bigram_codes*(text: string, result: var seq[int]) = 32 | case text.len 33 | of 0: discard 34 | of 1: result.add [text[0], ' '].Bigram.encode_bigram 35 | else: 36 | for i in 0..(text.len - 2): 37 | result.add Bigram.init(text, i).encode_bigram 38 | # result.add [text[^1], ' ' ].Bigram.encode_bigram 39 | 40 | proc to_bigram_codes*(text: string): seq[int] {.inline.} = 41 | to_bigram_codes(text, result) 42 | 43 | proc to_bigrams*(text: string): seq[string] = 44 | case text.len 45 | of 0: discard 46 | of 1: result.add text & " " 47 | else: 48 | for i in 0..(text.len - 2): 49 | result.add text[i..(i + 1)] 50 | 51 | # Trigrams ----------------------------------------------------------------------------------------- 52 | type Trigram = array[3, char] 53 | 54 | proc init*(_: type[Trigram], s: string, i: int): Trigram {.inline.} = 55 | [s[i], s[i+1], s[i+2]] 56 | 57 | proc `==`(a, b: Trigram): bool {.inline.} = 58 | a[0] == b[0] and a[1] == b[1] and a[2] == b[2] 59 | 60 | proc hash(t: Trigram): Hash {.inline.} = 61 | !$(t[0].hash !& t[1].hash !& t[2].hash) 62 | 63 | proc `$`(t: Trigram): string = 64 | t[0] & t[1] & t[2] 65 | 66 | var trigram_codes: Table[Trigram, int] 67 | template encode_trigram*(s: Trigram): int = 68 | trigram_codes.mget_or_put(s, trigram_codes.len) 69 | 70 | var trigram_rcodes: Table[int, Trigram] 71 | proc rebuild_trigram_rcodes = 72 | for g, c in trigram_codes: 73 | if c notin trigram_rcodes: trigram_rcodes[c] = g 74 | 75 | proc decode_trigram*(code: int): Trigram = 76 | if code notin trigram_rcodes: rebuild_trigram_rcodes() 77 | trigram_rcodes[code] 78 | 79 | proc to_trigram_codes*(text: string, result: var seq[int]) = 80 | case text.len 81 | of 0: discard 82 | of 1: result.add [text[0], ' ', ' '].Trigram.encode_trigram 83 | of 2: result.add [text[0], text[1], ' '].Trigram.encode_trigram 84 | else: 85 | for i in 0..(text.len - 3): 86 | result.add Trigram.init(text, i).encode_trigram 87 | 88 | proc to_trigram_codes*(text: string): seq[int] {.inline.} = 89 | to_trigram_codes(text, result) 90 | 91 | proc to_trigrams*(text: string): seq[string] {.inline.} = 92 | case text.len 93 | of 0: discard 94 | of 1: result.add text & " " 95 | of 2: result.add text & " " 96 | else: 97 | for i in 0..(text.len - 3): 98 | result.add text[i..(i + 2)] 99 | 100 | test "to_trigrams": 101 | check: 102 | "some text".to_trigram_codes == @[0, 1, 2, 3, 4, 5, 6] 103 | "some".to_trigram_codes == @[0, 1] -------------------------------------------------------------------------------- /ext/ext/gsl.nim: -------------------------------------------------------------------------------- 1 | {.passL: "-lgsl".} 2 | 3 | const libgsl = 4 | when defined(windows): "gsl.dll" 5 | elif defined(macosx): "libgsl.dylib" 6 | else: "libgsl.so" 7 | 8 | proc gsl_cdf_gaussian_P*(x: cdouble; sigma: cdouble): cdouble {.cdecl, importc, dynlib: libgsl.} 9 | 10 | type 11 | gsl_rng_type* {.bycopy.} = object 12 | name*: cstring 13 | max*: culong 14 | min*: culong 15 | size*: csize_t 16 | set*: proc (state: pointer; seed: culong) {.cdecl.} 17 | get*: proc (state: pointer): culong {.cdecl.} 18 | get_double*: proc (state: pointer): cdouble {.cdecl.} 19 | 20 | gsl_rng* {.bycopy.} = object 21 | `type`*: ptr gsl_rng_type 22 | state*: pointer 23 | 24 | var gsl_rng_default_seed* {.importc, dynlib: libgsl.}: culong 25 | var gsl_rng_default* {.importc, dynlib: libgsl.}: ptr gsl_rng_type 26 | 27 | proc gsl_rng_uniform*(r: ptr gsl_rng): cdouble {.cdecl, importc, dynlib: libgsl.} 28 | proc gsl_rng_alloc*(T: ptr gsl_rng_type): ptr gsl_rng {.cdecl, importc, dynlib: libgsl.} 29 | proc gsl_rng_free*(r: ptr gsl_rng) {.cdecl, importc, dynlib: libgsl.} 30 | proc gsl_ran_gaussian*(r: ptr gsl_rng; sigma: cdouble): cdouble {.cdecl, importc, dynlib: libgsl.} 31 | proc gsl_rng_uniform_int*(r: ptr gsl_rng; n: culong): culong {.cdecl, importc, dynlib: libgsl.} 32 | -------------------------------------------------------------------------------- /ext/ext/http_client.nim: -------------------------------------------------------------------------------- 1 | import std/[sugar, httpclient, strformat] 2 | import ./support, ./json, ./url, ./seqm, ./fallible, ./table, ./log 3 | from std/uri import nil 4 | 5 | let default_timeout_sec = 5 6 | let http_pool_size_warn = 100 7 | 8 | # with_client -------------------------------------------------------------------------------------- 9 | var clients_pool: Table[(string, string, int), HttpClient] 10 | template with_client(url: string, use_pool: bool, code) = 11 | if use_pool: 12 | if clients_pool.len > http_pool_size_warn: 13 | Log.init("http").with((size: clients_pool.len)).warn("clients pool is too large {size}") 14 | 15 | let parsed = Url.parse(url) 16 | let pool_key = (parsed.scheme, parsed.host, parsed.port) 17 | if pool_key notin clients_pool: clients_pool[pool_key] = new_http_client() 18 | let client {.inject.} = clients_pool[pool_key] 19 | return code 20 | else: 21 | let client {.inject.} = new_http_client() 22 | defer: client.close 23 | return code 24 | 25 | 26 | # http_get, http_post ------------------------------------------------------------------------------ 27 | proc http_get*( 28 | url: string, timeout_sec = default_timeout_sec, use_pool = false, 29 | headers: openarray[(string, string)] = @[] 30 | ): string = 31 | with_client(url, use_pool): 32 | client.timeout = timeout_sec * 1000 33 | client.headers = new_http_headers(headers) 34 | client.get_content(url) 35 | 36 | proc http_post*( 37 | url: string, content: string, timeout_sec = default_timeout_sec, use_pool = false, 38 | headers: openarray[(string, string)] = @[] 39 | ): string = 40 | with_client(url, use_pool): 41 | client.timeout = timeout_sec * 1000 42 | client.headers = new_http_headers(headers) 43 | client.post_content(url, content) 44 | 45 | 46 | # get_data, post_data ------------------------------------------------------------------------------ 47 | proc get_data*[Res](url: string, timeout_sec = default_timeout_sec, use_pool = false): Res = 48 | let resp = http_get(url, timeout_sec, use_pool, { "Content-Type": "application/json" }) 49 | resp.parse_json.json_to(Fallible[Res]).get 50 | 51 | proc post_data*[Req, Res](url: string, req: Req, timeout_sec = default_timeout_sec, use_pool = false): Res = 52 | let resp = http_post(url, req.to_json.to_s, timeout_sec, use_pool, { "Content-Type": "application/json" }) 53 | resp.parse_json.json_to(Fallible[Res]).get 54 | 55 | 56 | # post_batch --------------------------------------------------------------------------------------- 57 | type PostBatchItem*[T] = tuple[path: string, body: T] 58 | proc post_batch*[Req, Res]( 59 | url: string, requests: seq[PostBatchItem[Req]], timeout_sec = default_timeout_sec, use_pool = false 60 | ): seq[Fallible[Res]] = 61 | let data = http_post(url, requests.to_json.to_s, timeout_sec, use_pool, { "Content-Type": "application/json" }) 62 | let json = data.parse_json 63 | if json.kind == JObject and "is_error" in json: 64 | for _ in requests: 65 | result.add json.json_to(Fallible[Res]) 66 | else: 67 | for item in json: 68 | result.add item.json_to(Fallible[Res]) 69 | 70 | 71 | # build_url ---------------------------------------------------------------------------------------- 72 | proc build_url*(url: string, query: varargs[(string, string)]): string = 73 | if query.len > 0: url & "?" & uri.encode_query(query) 74 | else: url 75 | 76 | proc build_url*(url: string, query: tuple): string = 77 | var squery: seq[(string, string)] = @[] 78 | for k, v in query.field_pairs: squery.add((k, $v)) 79 | build_url(url, squery) 80 | 81 | test "build_url": 82 | assert build_url("http://some.com") == "http://some.com" 83 | assert build_url("http://some.com", { "a": "1", "b": "two" }) == "http://some.com?a=1&b=two" 84 | 85 | assert build_url("http://some.com", (a: 1, b: "two")) == "http://some.com?a=1&b=two" -------------------------------------------------------------------------------- /ext/ext/license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Petrushin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ext/ext/linalg.nim: -------------------------------------------------------------------------------- 1 | import std/sugar 2 | import ./support, ./math, ./seqm 3 | 4 | type Point2D = tuple[x: float, y: float] 5 | type Point3D = tuple[x: float, y: float, z: float] 6 | 7 | # inverse_distance_weighting ----------------------------------------------------------------------- 8 | const inverse_distance_weighting_minimal_distance = 1000.0 * MinFloatNormal 9 | func inverse_distance_weighting*(points: seq[Point3D], target: Point2D): float = 10 | let (x, y) = target 11 | 12 | # Ensuring approximated point inside of points 13 | let xis = points.pick(x) 14 | let yis = points.pick(y) 15 | assert((xis.min <= x) and (x <= xis.max), "x of approximated point lies outside of neighbours") 16 | assert((yis.min <= y) and (y <= yis.max), "y of approximated point lies outside of neighbours") 17 | 18 | # Calculating distances 19 | let distances = points.map((p) => ((p.x - x).pow(2) + (p.y - y).pow(2)).sqrt) 20 | 21 | # Handling special case when distance is 0 22 | let min_distance_i = distances.findi_min.get 23 | if distances[min_distance_i] < inverse_distance_weighting_minimal_distance: 24 | return points[min_distance_i].z 25 | 26 | # Calculating weights 27 | let weights = distances.map((d) => 1/d) 28 | 29 | # Calculating weighted sum 30 | result = weights.map((w, i) => w * points[i].z).sum / weights.sum 31 | assert result.is_number 32 | 33 | test "inverse_distance_weighting": 34 | assert inverse_distance_weighting( 35 | @[(3.0, 3.0, 1.0), (5.0, 3.0, 2.0), (5.0, 5.0, 2.0), (3.0, 5.0, 1.0)], (4.0, 4.0)) =~ 1.5 36 | assert inverse_distance_weighting( 37 | @[(3.0, 3.0, 1.0), (5.0, 3.0, 2.0), (5.0, 5.0, 2.0), (3.0, 5.0, 1.0)], (4.5, 4.5)) =~ 1.6496 38 | assert inverse_distance_weighting( 39 | @[(3.0, 3.0, 1.0), (3.0, 3.0, 1.0), (3.0, 3.0, 1.0), (3.0, 3.0, 1.0)], (3.0, 3.0)) =~ 1.0 40 | assert inverse_distance_weighting( 41 | @[(4.0, 2.0, 0.5), (4.0, 2.0, 0.5), (4.0, 2.0, 0.5), (4.0, 2.0, 0.5)], (4.0, 2.0)) =~ 0.5 42 | 43 | 44 | # doc({ 45 | # tags: ('Math', 'Approximation'], 46 | # title: 'Inverse Distance Weighting', 47 | # text: ` 48 | # Inverse Distance Weighted is a deterministic spatial interpolation approach to estimate an unknown value at a 49 | # location using some known values with corresponding weighted values. It's the weighted average, the weight 50 | # inverse proportional to the distnance between known and approximated point. 51 | 52 | # The approximation for value $x$ for function $z(x)$ found as: 53 | 54 | # $$z(x) : x \\rightarrow R$$ 55 | # $$z_x = {{\\sum z_i w_i} \\over \\sum w_i}$$ where 56 | # $$w_i = distance(x, x_i)^{-1}$$ 57 | 58 | # This approach frequently used in geography to interpolate the earth surface: 59 | 60 | # ![](math/idw.gif) 61 | # ` 62 | # }) -------------------------------------------------------------------------------- /ext/ext/memdb.nim: -------------------------------------------------------------------------------- 1 | import base 2 | 3 | template define_code*(Code, Source, Underneath) = 4 | type Code = distinct Underneath 5 | 6 | proc `$`*(code: Code): string {.borrow.} 7 | proc hash*(code: Code): Hash {.borrow.} 8 | proc `<`*(a, b: Code): bool {.borrow.} 9 | proc `<=`*(a, b: Code): bool {.borrow.} 10 | proc `==`*(a, b: Code): bool {.borrow.} 11 | proc `-`*(code: Code, dec: int): Code = (code.Underneath - dec.Underneath).Code 12 | proc `+`*(code: Epoch, inc: int): Code = (code.Underneath + inc.Underneath).Code 13 | 14 | var code_to_source: seq[Source] 15 | var source_to_code: Table[Source, Code] 16 | proc code*(source: Source): Code = 17 | result = source_to_code.mget_or_put(source, source_to_code.len.Code) 18 | if source_to_code.len > code_to_source.len: code_to_source.add source 19 | proc source*(code: Code): Source = 20 | code_to_source[code.Underneath] 21 | 22 | template define_int16_code*(Code, Source) = 23 | define_code Code, Source, int16 24 | 25 | when is_main_module: 26 | define_int16_code IdCode, string 27 | 28 | let zid = "Zeratul".code 29 | let tid = "Tassadar".code 30 | check (zid, tid) == (0.IdCode, 1.IdCode) 31 | check zid.source == "Zeratul" 32 | check tid.source == "Tassadar" -------------------------------------------------------------------------------- /ext/ext/parser.nim: -------------------------------------------------------------------------------- 1 | import base 2 | 3 | type 4 | Parser* = ref object 5 | text*: string 6 | i*: int 7 | warns*: seq[string] 8 | 9 | proc init*(_: type[Parser], text: string, i = 0): Parser = 10 | Parser(text: text, i: i) 11 | 12 | proc deep_copy*(pr: Parser): Parser = 13 | Parser(text: pr.text, i: pr.i, warns: pr.warns) 14 | 15 | proc get*(pr: Parser, shift = 0): Option[char] = 16 | let i = pr.i + shift 17 | if i >= 0 and i < pr.text.len: pr.text[i].some else: char.none 18 | 19 | iterator items*(pr: Parser, shift = 0): char = 20 | var i = pr.i + shift; let len = pr.text.len 21 | while i < len: 22 | yield pr.text[i] 23 | i.inc 24 | 25 | proc has*(pr: Parser, shift = 0): bool = 26 | let i = pr.i + shift 27 | i >= 0 and i < pr.text.len 28 | 29 | proc has_next*(pr: Parser): bool = 30 | pr.has(1) 31 | 32 | proc inc*(pr: Parser, by = 1) = 33 | pr.i = min(pr.text.len, pr.i + by) 34 | 35 | proc skip*(pr: Parser, fn: (char) -> bool, shift = 0) = 36 | pr.i += shift 37 | while pr.has: 38 | if not fn(pr.get.get): break 39 | pr.inc 40 | 41 | proc skip*(pr: Parser, s: set[char], shift = 0) = 42 | pr.skip(((c) => c in s), shift) 43 | 44 | proc consume*(pr: Parser, fn: (char) -> bool, shift = 0): string = 45 | pr.i += shift 46 | while pr.has: 47 | if not fn(pr.get.get): break 48 | result.add pr.get.get 49 | pr.inc 50 | 51 | proc consume*(pr: Parser, s: set[char], shift = 0): string = 52 | pr.consume(((c) => c in s), shift) 53 | 54 | test "consume": 55 | let pr = Parser.init("abbc", 1) 56 | check pr.consume({'b'}) == "bb" 57 | check pr.i == 3 58 | 59 | proc find*(pr: Parser, fn: (char) -> bool, shift = 0, limit = -1): int = 60 | var j = shift 61 | while true: 62 | if limit >= 0 and j > limit: break 63 | let i = pr.i + j 64 | if i > pr.text.high: break 65 | if fn(pr.text[i]): return j 66 | j.inc 67 | -1 68 | 69 | proc find*(pr: Parser, s: set[char], shift = 0, limit = -1): int = 70 | pr.find(((c) => c in s), shift = shift, limit = limit) 71 | 72 | proc rfind*(pr: Parser, fn: (char) -> bool, shift = 0, limit = -1): int = 73 | var j = shift 74 | while true: 75 | if limit >= 0 and j > limit: break 76 | let i = pr.i - j 77 | if i < 0: break 78 | if i < pr.text.high: # case when parser is finished 79 | if fn(pr.text[i]): return j 80 | j.inc 81 | -1 82 | 83 | proc rfind*(pr: Parser, s: set[char], shift = 0, limit = -1): int = 84 | pr.rfind(((c) => c in s), shift = shift, limit = limit) 85 | 86 | proc fget*(pr: Parser, fn: ((char) -> bool), shift = 0, limit = -1): Option[char] = 87 | let i = pr.find(fn, shift = shift, limit = limit) 88 | if i >= 0: return pr.get(i) 89 | 90 | proc fget*(pr: Parser, s: set[char], shift = 0, limit = -1): Option[char] = 91 | pr.fget(((c) => c in s), shift = shift, limit = limit) 92 | 93 | proc starts_with*(pr: Parser, s: string, shift = 0): bool = 94 | for j, c in s: 95 | let i = pr.i + shift + j 96 | if i > pr.text.high or s[j] != pr.text[i]: return false 97 | true 98 | 99 | test "find, fget": 100 | let pr = Parser.init("abcd", 1) 101 | check pr.find({'c'}) == 1 102 | check pr.fget({'c'}) == 'c' 103 | 104 | proc remainder*(pr: Parser): string = 105 | pr.text[pr.i..pr.text.high] 106 | 107 | # proc before_after*(pr: Parser): string = 108 | # # for debug 109 | # let before = pr.text[(pr.text.low, pr.i - 5).max..(pr.text.low, pr.i - 1).max] 110 | # let after = pr.text[(pr.text.high, pr.i + 1).min..(pr.text.high, pr.i + 5).min] 111 | # fmt"{before}|{pr.get}|{after}" -------------------------------------------------------------------------------- /ext/ext/persistence.nim: -------------------------------------------------------------------------------- 1 | import base 2 | 3 | proc read_from*[T](_: type[T], path: string): Option[T] = 4 | try: 5 | let json = fs.read path 6 | json.parse_json.json_to(T).some 7 | except: 8 | T.none 9 | 10 | proc write_to*[T](v: T, path: string): void = 11 | fs.write(path, v.to_json.to_s) 12 | 13 | 14 | # cache -------------------------------------------------------------------------------------------- 15 | type Cache[T] = object 16 | value: T 17 | timestamp: Time 18 | version: int 19 | 20 | let cache_version = 1 21 | 22 | proc cache*[T](path: string, expiration_sec: int, build: () -> T): T = 23 | proc build_and_write(): Cache[T] = 24 | let v = build() 25 | let cache = Cache(value: v, timestamp: Time.now(), version: cache_version) 26 | cache.write_to(path) 27 | cache 28 | var cached = Cache[T].read_from_optional(path, build_and_write) 29 | if (cached.version != cache_version) or ((Time.now.epoch - cached.timestamp.epoch) > expiration_sec): 30 | cached = build_and_write() 31 | cached.value -------------------------------------------------------------------------------- /ext/ext/ring_buffer.nim: -------------------------------------------------------------------------------- 1 | import base 2 | 3 | type RingBuffer*[T] = object 4 | data: seq[T] 5 | oldest, count: int 6 | 7 | proc init*[T](_: type[RingBuffer[T]]; size: int): RingBuffer[T] = 8 | RingBuffer[T](data: new_seq[T](size), oldest: 0, count: 0) 9 | 10 | proc init*[T](_: type[RingBuffer[T]]; data: openarray[T]): RingBuffer[T] = 11 | RingBuffer[T](data: data.to_seq, oldest: 0, count: data.len) 12 | 13 | proc len*(self: RingBuffer): int = self.count 14 | 15 | proc is_empty*(self: RingBuffer): bool = self.count == 0 16 | 17 | proc is_full*(self: RingBuffer): bool = self.count == self.data.len 18 | 19 | proc add*[T](self: var RingBuffer[T], v: T) = 20 | if self.is_full: 21 | self.data[self.oldest] = v 22 | self.oldest = (self.oldest + 1) mod self.data.len 23 | else: 24 | self.data[self.oldest + self.count] = v 25 | self.count += 1 26 | 27 | proc addget*[T](self: var RingBuffer[T], v: T): T = 28 | assert self.is_full, "can't get if buffer is not full" 29 | result = self.data[self.oldest] 30 | self.add(v) 31 | 32 | proc add*[T](self: var RingBuffer[T], values: openarray[T]) = 33 | for v in values: self.add v 34 | 35 | iterator items*[T](self: RingBuffer[T]): T = 36 | for i in 0..