cleanup and fixes for libnice support

This commit is contained in:
Stefan Salewski 2020-10-15 17:02:39 +02:00
commit 8966bd9ea1
13 changed files with 1252 additions and 449 deletions

View file

@ -1,6 +1,6 @@
= High level GTK3 (and GTK4) bindings for the Nim programming language = High level GTK3 (and GTK4) bindings for the Nim programming language
(c) Stefan Salewski (c) Stefan Salewski
//Version 0.8.2 //Version 0.8.3
:experimental: :experimental:
:imagesdir: http://ssalewski.de/tmp :imagesdir: http://ssalewski.de/tmp
:source-highlighter: pygments :source-highlighter: pygments
@ -25,6 +25,56 @@ with code inserted from files directly.
NOTE: This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf, E. Bassi and other _Nim_ and _GTK/Gnome_ developers. NOTE: This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf, E. Bassi and other _Nim_ and _GTK/Gnome_ developers.
The `combinatorics` module was kindly provided by R. Behrends. The `combinatorics` module was kindly provided by R. Behrends.
NOTE: We are currently preparing v0.8.3. You may install it with command [black yellow-background]#nimble install gintro@#head#.
This version will have some internal cleanup, which removes the temporary
Array types. In the past we used that names to indicate array parameters, it was working well, but still
it was ugly using names to indicates data types. So we fixed that. And we have added some better support for
GList parameter. GLists are now converted to Nim seqs and vice versa. Not everywhere still, and this conversion
is still untested, as GList parameters are used most of the time in exotic functions only. Another change is that
we have support for libnice by a request of a user from Japan. We added the sdp_example.nim converted from
a C example, it seems to compile and run. And finally with v0.8.3 the examples from the GTK4 book should work.
For the GTK3 examples we had to do some minimal fixes, so you may have to do similar fixes for your own code as well.
For gintro/examples/gtk3 this was
----
# examples modifications for v0.8.3:
$ diff gintronew/examples/gtk3 gintro/examples/gtk3
diff gintronew/examples/gtk3/celldatafunction.nim gintro/examples/gtk3/celldatafunction.nim
3c3
< import gintro/[gtk, gobject, glib]
---
> import gintro/[gtk, gobject]
67c67
< let store = newListStore(Columns, cast[ptr GType](unsafeaddr gtype)) # ugly cast
---
> let store = newListStore(Columns, cast[pointer](unsafeaddr gtype)) # ugly cast
diff gintronew/examples/gtk3/css_colored_listview.nim gintro/examples/gtk3/css_colored_listview.nim
64c64
< var store = newListStore(Columns, cast[ptr GType]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
---
> var store = newListStore(Columns, cast[pointer]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
diff gintronew/examples/gtk3/gearsmenu.nim gintro/examples/gtk3/gearsmenu.nim
114c114
< echo fmt"GTK Version {getMajorVersion()}.{getMinorVersion()}.{getMicroVersion()}"
---
> echo fmt"GTK Version {majorVersion()}.{minorVersion()}.{microVersion()}"
diff gintronew/examples/gtk3/listview.nim gintro/examples/gtk3/listview.nim
61c61
< let store = newListStore(N_COLUMNS, cast[ptr GType]( unsafeaddr gtype)) # cast due to bug in gtk.nim
---
> let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim
diff gintronew/examples/gtk3/menubar.nim gintro/examples/gtk3/menubar.nim
101c101
< echo "GTK Version $1.$2.$3" % [$getMajorVersion(), $getMinorVersion(), $getMicroVersion()]
---
> echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()]
diff gintronew/examples/gtk3/overlay_tree1.nim gintro/examples/gtk3/overlay_tree1.nim
71c71
< let store = newListStore(Columns, cast[ptr GType](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
---
> let store = newListStore(Columns, cast[pointer](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
----
NOTE: This is version 0.8.2 of gintro. It is only a fix for recent gstreamer 1.18 which added some uncommon NOTE: This is version 0.8.2 of gintro. It is only a fix for recent gstreamer 1.18 which added some uncommon
gobject-introspection stuff which brook the install of 0.8.1 for a few users. We where not yet able to gobject-introspection stuff which brook the install of 0.8.1 for a few users. We where not yet able to
verify that the gst example which is bases on the gst C tutorial1 works with gstreamer 1.18. verify that the gst example which is bases on the gst C tutorial1 works with gstreamer 1.18.

View file

@ -1,6 +1,6 @@
# This example shows how to apply a CellDataFunc to a GtkTreeView # This example shows how to apply a CellDataFunc to a GtkTreeView
# C example code was provided by A.Krause in chapter 8 of his book # C example code was provided by A.Krause in chapter 8 of his book
import gintro/[gtk, gobject] import gintro/[gtk, gobject, glib]
const const
Color = 0 Color = 0
@ -64,7 +64,7 @@ proc main =
let treeview = newTreeView() let treeview = newTreeView()
setupTreeView(treeview) setupTreeView(treeview)
let gtype = typeFromName("gchararray") let gtype = typeFromName("gchararray")
let store = newListStore(Columns, cast[pointer](unsafeaddr gtype)) # ugly cast let store = newListStore(Columns, cast[ptr GType](unsafeaddr gtype)) # ugly cast
## Add all of the products to the GtkListStore. ## Add all of the products to the GtkListStore.
for i in 0 ..< 6: for i in 0 ..< 6:
for j in 0 ..< 6: for j in 0 ..< 6:

View file

@ -61,7 +61,7 @@ proc main() =
var iter: TreeIter var iter: TreeIter
var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"), var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
typeFromName("gchararray")] typeFromName("gchararray")]
var store = newListStore(Columns, cast[pointer]( unsafeaddr h)) # cast is ugly, we should fix it in bindings. var store = newListStore(Columns, cast[ptr GType]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"] let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
for i, n in progNames: for i, n in progNames:
store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original

View file

@ -111,7 +111,7 @@ proc main =
let app = newApplication("app.example") let app = newApplication("app.example")
connect(app, "startup", appStartup) connect(app, "startup", appStartup)
connect(app, "activate", appActivate) connect(app, "activate", appActivate)
echo fmt"GTK Version {majorVersion()}.{minorVersion()}.{microVersion()}" echo fmt"GTK Version {getMajorVersion()}.{getMinorVersion()}.{getMicroVersion()}"
let status = run(app) let status = run(app)
quit(status) quit(status)

View file

@ -58,7 +58,7 @@ proc initList(list: TreeView) =
column.addAttribute(renderer, "text", LIST_ITEM) column.addAttribute(renderer, "text", LIST_ITEM)
discard list.appendColumn(column) discard list.appendColumn(column)
let gtype = gStringGetType() # typeFromName("gchararray") let gtype = gStringGetType() # typeFromName("gchararray")
let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim let store = newListStore(N_COLUMNS, cast[ptr GType]( unsafeaddr gtype)) # cast due to bug in gtk.nim
list.setModel(store) list.setModel(store)
proc appActivate(app: Application) = proc appActivate(app: Application) =

View file

@ -98,7 +98,7 @@ proc main =
let app = newApplication("app.example") let app = newApplication("app.example")
connect(app, "startup", appStartup) connect(app, "startup", appStartup)
connect(app, "activate", appActivate) connect(app, "activate", appActivate)
echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()] echo "GTK Version $1.$2.$3" % [$getMajorVersion(), $getMinorVersion(), $getMicroVersion()]
let status = run(app) let status = run(app)
quit(status) quit(status)

View file

@ -68,7 +68,7 @@ proc main =
var iter: TreeIter var iter: TreeIter
let h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"), let h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
typeFromName("gchararray")] typeFromName("gchararray")]
let store = newListStore(Columns, cast[pointer](unsafeaddr h)) # cast is ugly, we should fix it in bindings. let store = newListStore(Columns, cast[ptr GType](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"] let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
for i, n in progNames: for i, n in progNames:
store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original

View file

@ -0,0 +1,219 @@
# https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/examples/sdp-example.c
# nim c --gc:arc sdp_example.nim
#
# Example using libnice to negotiate a UDP connection between two clients,
# possibly on the same network or behind different NATs and/or stateful
# firewalls.
#
# Run two clients, one controlling and one controlled:
# ./sdp-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
# ./sdp-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
import gintro/[gtk, glib, gobject, nice]
import gintro/gio except stdin
from strutils import `%`
from os import paramCount, paramStr
from strutils import parseInt
var
gloop: glib.MainLoop
stunAddr: string
stunPort: int
controlling: int
exitThread: bool
candidateGatheringDone: bool
negotiationDone: bool
gatherMutex: glib.Mutex
negotiateMutex: glib.Mutex
gatherCond: glib.Cond
negotiateCond: glib.Cond
stateName = ["disconnected", "gathering", "connecting", "connected", "ready", "failed"]
proc gDebug(s: string) =
stdout.write(s)
flushFile(stdout)
proc gError(s: string) =
stderr.write(s)
flushFile(stderr)
proc toStringVal(s: string): Value =
let gtype = gStringGetType()
discard init(result, gtype)
setString(result, s)
proc toBoolVal(b: bool): Value =
let gtype = gBooleanGetType()
discard init(result, gtype)
setBoolean(result, b)
proc toIntVal(i: int): Value =
let gtype = typeFromName("gint")
discard init(result, gtype)
setInt(result, i)
proc cbCandidateGatheringDone(agent: nice.Agent; streamID: int) =
gDebug("SIGNAL candidate gathering done\n")
glib.lock(gatherMutex)
candidateGatheringDone = true
glib.signal(gatherCond)
glib.unlock(gatherMutex)
proc cbComponentStateChanged(agent: nice.Agent; streamID: int; componentID: int; state: int) =
gDebug("SIGNAL: state changed $1 $2 $3[$4]\n" % [$streamID, $componentID, stateName[state.ord], $state])
if state == nice.ComponentState.ready.ord:
glib.lock(negotiateMutex)
negotiationDone = true
glib.signal(negotiateCond)
glib.unlock(negotiateMutex)
elif state == nice.ComponentState.failed.ord:
glib.quit(gloop)
# proc nice_agent_attach_recv*(self: ptr Agent00; streamID: cuint; componentID: cuint; ctx: ptr glib.MainContext;
# fn: AgentRecvFunc; data: pointer): gboolean {.importc, libprag.}
#
# this has to be a low level function!
# AgentRecvFunc* = proc (agent: ptr Agent00; streamId: uint32; componentId: uint32; len: uint32;
# buf: cstring; userData: pointer) {.cdecl.}
proc cbNiceRecv(agent: ptr nice.Agent00; streamID: uint32; componentID: uint32; len: uint32; buf: cstring; userData: pointer) {.cdecl.} =
if len == 1 and buf[0] == '\x00':
glib.quit(gloop)
discard stdout.writeBuffer(buf, len)
flushfile(stdout)
proc exampleThread(data: pointer): pointer {.cdecl.} =
var
agent: nice.Agent
ioStdin: glib.IOChannel
streamID: int
line: string
sdp: string
sdp64: string
#when defined(G_OS_WIN32):
# io_stdin = g_io_channel_win32_new_fd(_fileno(stdin))
#else:
ioStdin = unixNew(getFileHandle(stdin))
discard setFlags(ioStdin, glib.IOFlags.nonBlock)
# Create the nice agent
agent = nice.newAgent(glib.getContext(gloop), nice.CompatibilityRfc5245)
if agent == nil:
gError("Failed to create agent")
# Set the STUN settings and controlling mode
if stunAddr.len > 0:
setProperty(agent, "stun-server", toStringVal(stunAddr))
setProperty(agent, "stun-server-port", toIntVal(stunPort))
setProperty(agent, "controlling-mode", toIntVal(controlling))
# Connect to the signals
agent.connect("candidate-gathering-done", cbCandidateGatheringDone)
agent.connect("component-state-changed", cbComponentStateChanged)
# Create a new stream with one component
streamID = addStream(agent, 1)
if streamID == 0:
gError("Failed to add stream")
discard setStreamName(agent, streamID, "text")
# Attach to the component to receive the data
# Without this call, candidates cannot be gathered
discard attachRecv(agent, streamID, 1, getContext(gloop), cbNiceRecv, nil)
# Start gathering local candidates
if not gatherCandidates(agent, stream_id):
gError("Failed to start candidate gathering")
gDebug("waiting for candidate-gathering-done signal...")
lock(gatherMutex)
while not exitThread and not candidateGatheringDone:
wait(gatherCond, gatherMutex)
unlock(gatherMutex)
block gotoEnd:
if exitThread:
break gotoEnd
# Candidate gathering is done. Send our local candidates on stdout
sdp = generateLocalSdp(agent)
stdout.write("Generated SDP from agent :\n$1\n\n" % [sdp])
stdout.write("Copy the following line to remote client:\n")
sdp64 = base64Encode(sdp)
stdout.write("\n $1\n", sdp64)
# Listen on stdin for the remote candidate list
stdout.write("Enter remote data (single line, no wrapping):\n")
stdout.write("> ")
flushFile(stdout)
while not exitThread:
var terminatorPos: uint64
var length: uint64
var s: IOStatus = readLine(ioStdin, line, length, terminatorPos)
if s == IOStatus.normal:
var sdpLen: uint64
let sdpxxx = base64Decode(line, sdpLen) #seq[uint8]
sdp = cast[string](sdpxxx)
# Parse remote candidate list and set it on the agent
if sdp.len > 0 and parseRemoteSdp(agent, sdp) > 0:
break
else:
write(stderr, "ERROR: failed to parse remote data\n")
write(stdout, "Enter remote data (single line, no wrapping):\n")
stdout.write("> ")
flushFile(stdout)
elif s == IOStatus.again:
usleep(100000)
gDebug("waiting for state READY or FAILED signal...")
lock(negotiateMutex)
while not exitThread and not negotiationDone:
wait(negotiateCond, negotiateMutex)
unlock(negotiateMutex)
if exitThread:
break gotoEnd
# Listen to stdin and send data written to it
stdout.write("\nSend lines to remote (Ctrl-D to quit):\n")
stdout.write("> ")
flushFile(stdout)
while not exitThread:
var terminatorPos: uint64
var length: uint64
var s: IOStatus = readLine(ioStdin, line, length, terminatorPos);
if s == IOStatus.normal:
discard send(agent, streamID, 1, line.len, line)
stdout.write("> ")
flushFile(stdout)
elif s == IOStatus.again:
usleep(100000)
else:
# Ctrl-D was pressed.
discard send(agent, stream_id, 1, 1, "\x00")
break
# END label
quit(gloop)
return nil
proc main =
echo paramCount()
for i in 0 .. paramCount():
echo paramStr(i)
let argc = paramCount() + 1
var argv = newSeq[string](argc)
for i in 0 ..< argc:
argv[i] = paramStr(i)
var gexamplethread: glib.Thread
# Parse arguments
if argc > 4 or argc < 2 or argv[1].len > 1: # argv[1][1] != '\x00':
write(stderr, "Usage: $1 0|1 stun_addr [stun_port]\n" % [argv[0]])
quit(QuitFailure)
controlling = argv[1][0].ord - '0'.ord
if controlling != 0 and controlling != 1:
write(stderr, "Usage: $1 0|1 stun_addr [stun_port]\n" % [argv[0]])
quit(QuitFailure)
if argc > 2:
stun_addr = argv[2]
if argc > 3:
stun_port = parseInt(argv[3])
else:
stun_port = 3478
gDebug("Using stun server \'[$1]:$2\'\n" % [$stunAddr, $stunPort])
gio.networkingInit()
gloop = newMainLoop(nil, false)
# Run the mainloop and the example thread
exitThread = false
gexamplethread = newThread("example thread", exampleThread, nil)
run(gloop)
exitThread = true
discard join(gexamplethread)
quit(QuitSuccess)
main()

View file

@ -117,7 +117,7 @@ proc main =
content.setVexpand content.setVexpand
box.append(content) box.append(content)
let footer = newActionBar() let footer = newActionBar()
footer.setCenterWidget(newCheckButtonWithLabel("Middle")) footer.setCenterWidget(newCheckButton("Middle"))
let button = newToggleButtonWithLabel("Custom") let button = newToggleButtonWithLabel("Custom")
button.connect("clicked", changeHeader, window) button.connect("clicked", changeHeader, window)
footer.packStart(button) footer.packStart(button)

View file

@ -1,6 +1,6 @@
# Package # Package
version = "0.8.2" version = "0.8.3"
author = "Stefan Salewski" author = "Stefan Salewski"
description = "High level GObject-Introspection based GTK3/GTK4 bindings" description = "High level GObject-Introspection based GTK3/GTK4 bindings"
license = "MIT" license = "MIT"

View file

@ -1,4 +1,5 @@
from sequtils import keepItIf #from sequtils import keepItIf
import sequtils
const RecSep = "!" # Record separator for entries in gisup.nim, also defined in gen.nim const RecSep = "!" # Record separator for entries in gisup.nim, also defined in gen.nim
# caution: $$ due to string interpolation # caution: $$ due to string interpolation
@ -188,6 +189,33 @@ proc $1$2 {.cdecl.} =
r1s.add(resu & "\n") r1s.add(resu & "\n")
all = all.replace(")", "; user_data: pointer)") all = all.replace(")", "; user_data: pointer)")
#echo all
if all.find("00Array") >= 0:
var h = all.split(';')
for el in mitems(h):
var a, b: string
(a, b) = el.split(": ")
if b.find("Array") >= 0:
b = "ptr " & b.replace("Array")
el = a & ": " & b
all = h.join(";")
#echo all
# https://forum.nim-lang.org/t/6775#42155
const matcher = "Array" # is this good enough?
var ra: string
for (elem, isSep) in all.tokenize(Whitespace + {',', ';'}):
if elem.endsWith(matcher):
ra.add("ptr " & elem[0 .. ^(matcher.len + 1)])
else:
ra.add(elem)
#echo ra
#assert ra == all
r1s = r1s % [$procNameCdecl, all, $p, wts, ats] r1s = r1s % [$procNameCdecl, all, $p, wts, ats]
#echo r1s #echo r1s
@ -449,4 +477,4 @@ $1(column, renderer, $6)
""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] """ % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy]
result = parseStmt(r0s & r1s & r2s) result = parseStmt(r0s & r1s & r2s)
# 429 lines # 429 lines echo

File diff suppressed because it is too large Load diff

292
tests/maxby.nim Normal file
View file

@ -0,0 +1,292 @@
# https://forum.nim-lang.org/t/4763
proc maxIndexBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): int =
## Returns the index of the maximum value of all elements in s.
## Comparison is done by cmp() proc.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].maxIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 1
if s.len > 0:
var maxVal = s[0]
for i in 1 .. s.high:
if cmp(s[i], maxVal) > 0:
result = i
maxVal = s[i]
proc minIndexBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): int =
## Returns the index of the minimum value of all elements in s.
## Comparison is done by cmp() proc.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 3
if s.len > 0:
var minVal = s[0]
for i in 1 .. s.high:
if cmp(s[i], minVal) < 0:
result = i
minVal = s[i]
# see system.sortedByIt()
template maxIndexByIt*(seq1, op: untyped): untyped =
## Returns the maximum index of all elements in s.
## Comparison is done by it expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].maxIndexByIt(it.len) == 1
var result = maxIndexBy(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y
let b = op
result = cmp(a, b))
result
template minIndexByIt*(seq1, op: untyped): untyped =
## Returns the minimum index of all elements in s.
## Comparison is done by it expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minIndexByIt(it.len) == 3
var result = minIndexBy(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y
let b = op
result = cmp(a, b))
result
template maxIndex*(seq1, el, op: untyped): untyped =
## Returns the maximum index of all elements in s.
## Comparison is done by custom expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].maxIndex(el, el.len) == 1
var result = maxIndexBy(seq1, proc(x, y: type(seq1[0])): int =
var el {.inject.} = x
let a = op
el = y
let b = op
result = cmp(a, b))
result
template minIndex*(seq1, el, op: untyped): untyped =
## Returns the minimum index of all elements in s.
## Comparison is done by custom expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minIndex(el, el.len) == 3
var result = minIndexBy(seq1, proc(x, y: type(seq1[0])): int =
var el {.inject.} = x
let a = op
el = y
let b = op
result = cmp(a, b))
result
func maxValueBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): T =
## Returns the maximum value of all elements in s.
## Comparison is done by cmp() proc.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].maxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "Nim"
assert(s.len > 0)
result = s[0]
if s.len > 0:
for i in 1 .. s.high:
if cmp(s[i], result) > 0:
result = s[i]
func minValueBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): T =
## Returns the minimum value of all elements in s.
## Comparison is done by cmp() proc.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "D"
assert(s.len > 0)
result = s[0]
if s.len > 0:
for i in 1 .. s.high:
if cmp(s[i], result) < 0:
result = s[i]
template maxValueByIt*(seq1, op: untyped): untyped =
## Returns the maximum value of all elements in s.
## Comparison is done by it expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].maxValueByIt(it.len) == "Nim"
var result = maxValueBy(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y
let b = op
result = cmp(a, b))
result
template minValueByIt*(seq1, op: untyped): untyped =
## Returns the minimum value of all elements in s.
## Comparison is done by it expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minValueByIt(it.len) == "D"
var result = minValueBy(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y
let b = op
result = cmp(a, b))
result
template maxValue*(seq1, el, op: untyped): untyped =
## Returns the maximum value of all elements in s.
## Comparison is done by custom expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].maxValue(el, el.len) == "Nim"
var result = maxValueBy(seq1, proc(x, y: type(seq1[0])): int =
var el {.inject.} = x
let a = op
el = y
let b = op
result = cmp(a, b))
result
template minValue*(seq1, el, op: untyped): untyped =
## Returns the minimum value of all elements in s.
## Comparison is done by custom expression
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minValue(el, el.len) == "D"
var result = minValueBy(seq1, proc(x, y: type(seq1[0])): int =
var el {.inject.} = x
let a = op
el = y
let b = op
result = cmp(a, b))
result
func minMaxValueBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): (T, T) =
## Returns the minimum and maximum values of all elements in s.
## Comparison is done by cmp() proc.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minMaxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == ("D", "Nim")
assert(s.len > 0)
if s.len > 0:
result[0] = s[0]
result[1] = s[0]
for i in 1 .. s.high:
if cmp(result[1], s[i]) < 0:
result[1] = s[i]
elif cmp(result[0], s[i]) > 0:
result[0] = s[i]
template minMaxValueByIt*(seq1, op: untyped): untyped =
## Returns the minimum and maximum values of all elements in s.
## Comparison is done by it expression.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minMaxValueByIt(it.len) == ("D", "Nim")
var result = minMaxValueBy(seq1, proc(x, y: type(seq1[0])): int =
var it {.inject.} = x
let a = op
it = y
let b = op
result = cmp(a, b))
result
template minMaxValue*(seq1, el, op: untyped): untyped =
## Returns the minimum and maximum values of all elements in s.
## Comparison is done by custom expression.
##
## Example:
##
## .. code-block:
## assert ["Go", "Nim", "C++", "D"].minMaxValue(el, el.len) == ("D", "Nim")
var result = minMaxValueBy(seq1, proc(x, y: type(seq1[0])): int =
var el {.inject.} = x
let a = op
el = y
let b = op
result = cmp(a, b))
result
when isMainModule:
type
Person = object
name: string
age: int
var people: seq[Person] = @[Person(name: "Eve", age: 11), Person(name: "Sarah", age: 25), Person(name: "Anna", age: 23)]
var (min, max) = people.minMaxValue(p, p.name)
echo "Min: ", min.name, ", ", min.age
echo "Max: ", max.name, ", ", max.age
var m = people.maxValueByIt(it.name.len)
echo "Max by length of name: ", m.name
var h = people.minIndexBy(proc (x,y: Person): int = cmp(x.name.len, y.name.len))
echo "Min index by length of name: ", h
assert ["Go", "Nim", "C++", "D"].maxIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 1
assert ["Go", "Nim", "C++", "D"].minIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 3
assert ["Go", "Nim", "C++", "D"].maxIndexByIt(it.len) == 1
assert ["Go", "Nim", "C++", "D"].minIndexByIt(it.len) == 3
assert ["Go", "Nim", "C++", "D"].maxIndex(el, el.len) == 1
assert ["Go", "Nim", "C++", "D"].minIndex(el, el.len) == 3
assert ["Go", "Nim", "C++", "D"].maxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "Nim"
assert ["Go", "Nim", "C++", "D"].minValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "D"
assert ["Go", "Nim", "C++", "D"].maxValueByIt(it.len) == "Nim"
assert ["Go", "Nim", "C++", "D"].minValueByIt(it.len) == "D"
assert ["Go", "Nim", "C++", "D"].maxValue(el, el.len) == "Nim"
assert ["Go", "Nim", "C++", "D"].minValue(el, el.len) == "D"
assert ["Go", "Nim", "C++", "D"].minMaxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == ("D", "Nim")
assert ["Go", "Nim", "C++", "D"].minMaxValueByIt(it.len) == ("D", "Nim")
assert ["Go", "Nim", "C++", "D"].minMaxValue(el, el.len) == ("D", "Nim")