mirror of https://github.com/japl-lang/jale.git
Creation of plugins
moved defaults to plugins, cleaned it a bit up moved all history related things to plugins history wip changed defaults created examples dir, added multiline single file editing example (no file working yet)
This commit is contained in:
parent
ad508cd9f3
commit
3dd77ea1d9
|
@ -1,2 +1,3 @@
|
||||||
main
|
examples/interactive
|
||||||
|
examples/editor
|
||||||
getkey
|
getkey
|
||||||
|
|
49
defaults.nim
49
defaults.nim
|
@ -1,49 +0,0 @@
|
||||||
import editor
|
|
||||||
import keycodes
|
|
||||||
import multiline
|
|
||||||
import event
|
|
||||||
import tables
|
|
||||||
import templates
|
|
||||||
|
|
||||||
proc populateDefaults*(editor: LineEditor) =
|
|
||||||
editor.bindEvent(jeKeypress):
|
|
||||||
if editor.lastKeystroke > 31 and editor.lastKeystroke < 127:
|
|
||||||
let ch = char(editor.lastKeystroke)
|
|
||||||
editor.content.insert($ch)
|
|
||||||
editor.bindKey("left"):
|
|
||||||
editor.content.left()
|
|
||||||
editor.bindKey("right"):
|
|
||||||
editor.content.right()
|
|
||||||
editor.bindKey("up"):
|
|
||||||
editor.content.up()
|
|
||||||
editor.bindKey("down"):
|
|
||||||
editor.content.down()
|
|
||||||
editor.bindKey("home"):
|
|
||||||
editor.content.home()
|
|
||||||
editor.bindKey("end"):
|
|
||||||
editor.content.`end`()
|
|
||||||
editor.bindKey("pageup"):
|
|
||||||
editor.content.vhome()
|
|
||||||
editor.bindKey("pagedown"):
|
|
||||||
editor.content.vend()
|
|
||||||
editor.bindKey("backspace"):
|
|
||||||
editor.content.backspace()
|
|
||||||
editor.bindKey("delete"):
|
|
||||||
editor.content.delete()
|
|
||||||
editor.bindKey("enter"):
|
|
||||||
if editor.content.Y() == editor.content.high() and editor.content.getLine(editor.content.high()) == "":
|
|
||||||
editor.finish()
|
|
||||||
else:
|
|
||||||
editor.content.enter()
|
|
||||||
editor.bindKey("ctrl+c"):
|
|
||||||
editor.finish()
|
|
||||||
editor.events.call(jeQuit)
|
|
||||||
editor.bindKey("ctrl+d"):
|
|
||||||
if editor.content.getContent() == "":
|
|
||||||
editor.finish()
|
|
||||||
editor.events.call(jeQuit)
|
|
||||||
editor.bindKey("shiftup"):
|
|
||||||
editor.historyMove(-1)
|
|
||||||
editor.bindKey("shiftdown"):
|
|
||||||
editor.historyMove(1)
|
|
||||||
|
|
65
editor.nim
65
editor.nim
|
@ -11,18 +11,23 @@ import renderer
|
||||||
|
|
||||||
type
|
type
|
||||||
JaleEvent* = enum
|
JaleEvent* = enum
|
||||||
jeKeypress, jeQuit, jeFinish, jeHistoryChange
|
jeKeypress, jeQuit, jeFinish, jePreRead, jePostRead
|
||||||
|
|
||||||
LineEditor* = ref object
|
LineEditor* = ref object
|
||||||
content*: Multiline
|
# permanents
|
||||||
historyIndex*: int # TODO to be private
|
|
||||||
history*: seq[Multiline] # TODO to be private
|
|
||||||
keystrokes*: Event[int]
|
keystrokes*: Event[int]
|
||||||
events*: Event[JaleEvent]
|
events*: Event[JaleEvent]
|
||||||
prompt*: string
|
prompt*: string
|
||||||
|
|
||||||
|
# permanent internals: none
|
||||||
|
|
||||||
|
# per-read contents
|
||||||
|
content*: Multiline
|
||||||
lastKeystroke*: int
|
lastKeystroke*: int
|
||||||
|
# per-read internals
|
||||||
finished: bool
|
finished: bool
|
||||||
rendered: int # how many lines were printed last full refresh
|
rendered: int # how many lines were printed last full refresh
|
||||||
|
forceRedraw: bool
|
||||||
|
|
||||||
# getter/setter sorts
|
# getter/setter sorts
|
||||||
|
|
||||||
|
@ -34,48 +39,29 @@ proc finish*(le: LineEditor) =
|
||||||
# can be overwritten to false, inside the event
|
# can be overwritten to false, inside the event
|
||||||
le.events.call(jeFinish)
|
le.events.call(jeFinish)
|
||||||
|
|
||||||
|
proc forceRedraw*(le: LineEditor) =
|
||||||
|
le.forceRedraw = true
|
||||||
|
|
||||||
# constructor
|
# constructor
|
||||||
|
|
||||||
proc newLineEditor*: LineEditor =
|
proc newLineEditor*: LineEditor =
|
||||||
new(result)
|
new(result)
|
||||||
result.content = newMultiline()
|
result.content = newMultiline()
|
||||||
result.history = @[]
|
|
||||||
result.history.add(result.content)
|
|
||||||
result.historyIndex = 0
|
|
||||||
result.keystrokes.new()
|
result.keystrokes.new()
|
||||||
result.events.new()
|
result.events.new()
|
||||||
result.prompt = ""
|
result.prompt = ""
|
||||||
result.rendered = 0
|
result.rendered = 0
|
||||||
|
result.lastKeystroke = -1
|
||||||
|
result.forceRedraw = false
|
||||||
|
|
||||||
# priv/pub methods
|
# priv/pub methods
|
||||||
|
|
||||||
proc historyMove*(editor: LineEditor, delta: int) =
|
|
||||||
# broken behaviour: history should not be modified, it should clone
|
|
||||||
# the modified one that is eventually submitted and add it to the end
|
|
||||||
# TODO
|
|
||||||
if editor.historyIndex + delta < 0:
|
|
||||||
editor.content = editor.history[0]
|
|
||||||
editor.historyIndex = 0
|
|
||||||
elif editor.historyIndex + delta >= editor.history.high():
|
|
||||||
editor.content = editor.history[editor.history.high()]
|
|
||||||
editor.historyIndex = editor.history.high()
|
|
||||||
else:
|
|
||||||
editor.content = editor.history[editor.historyIndex + delta]
|
|
||||||
editor.historyIndex += delta
|
|
||||||
editor.events.call(jeHistoryChange)
|
|
||||||
|
|
||||||
|
|
||||||
proc reset(editor: LineEditor) =
|
proc reset(editor: LineEditor) =
|
||||||
editor.unfinish()
|
editor.unfinish()
|
||||||
editor.rendered = 0
|
editor.rendered = 0
|
||||||
|
|
||||||
proc flush*(editor: LineEditor) =
|
|
||||||
# kinda like reset, it moves the current element one down in history
|
|
||||||
# and adds a new one
|
|
||||||
editor.reset()
|
|
||||||
editor.content = newMultiline()
|
editor.content = newMultiline()
|
||||||
editor.history.add(editor.content)
|
editor.lastKeystroke = -1
|
||||||
editor.historyIndex = editor.history.high()
|
editor.forceRedraw = false
|
||||||
|
|
||||||
proc render(editor: LineEditor, line: int = -1, hscroll: bool = true) =
|
proc render(editor: LineEditor, line: int = -1, hscroll: bool = true) =
|
||||||
var y = line
|
var y = line
|
||||||
|
@ -122,18 +108,13 @@ proc moveCursorToEnd(editor: LineEditor) =
|
||||||
cursorDown(editor.content.high() - editor.content.Y)
|
cursorDown(editor.content.high() - editor.content.Y)
|
||||||
write stdout, "\n"
|
write stdout, "\n"
|
||||||
|
|
||||||
# TODO don't use globals, but allow for event removal
|
|
||||||
var histchange = false
|
|
||||||
proc changeHistory =
|
|
||||||
histchange = true
|
|
||||||
|
|
||||||
proc read*(editor: LineEditor): string =
|
proc read*(editor: LineEditor): string =
|
||||||
|
|
||||||
|
editor.events.call(jePreRead)
|
||||||
|
|
||||||
# starts at the top, full render moves it into the right y
|
# starts at the top, full render moves it into the right y
|
||||||
editor.fullRender()
|
editor.fullRender()
|
||||||
|
|
||||||
# TODO: must remove event at end
|
|
||||||
# otherwise there'll be more instances of the same
|
|
||||||
editor.events.add(jeHistoryChange, changeHistory)
|
|
||||||
|
|
||||||
while not editor.finished:
|
while not editor.finished:
|
||||||
|
|
||||||
|
@ -149,14 +130,16 @@ proc read*(editor: LineEditor): string =
|
||||||
editor.keystrokes.call(key)
|
editor.keystrokes.call(key)
|
||||||
editor.events.call(jeKeypress)
|
editor.events.call(jeKeypress)
|
||||||
# redraw everything if y changed
|
# redraw everything if y changed
|
||||||
if histchange or preY != editor.content.Y:
|
if editor.forceRedraw or preY != editor.content.Y:
|
||||||
# move to the top
|
# move to the top
|
||||||
if preY > 0:
|
if preY > 0:
|
||||||
cursorUp(preY)
|
cursorUp(preY)
|
||||||
# move to the right y
|
# move to the right y
|
||||||
editor.fullRender()
|
editor.fullRender()
|
||||||
if histchange:
|
if editor.forceRedraw:
|
||||||
histchange = false
|
editor.forceRedraw = false
|
||||||
|
|
||||||
|
editor.events.call(jePostRead)
|
||||||
|
|
||||||
# move cursor to end
|
# move cursor to end
|
||||||
editor.moveCursorToEnd()
|
editor.moveCursorToEnd()
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import ../plugin/defaults
|
||||||
|
import ../editor
|
||||||
|
import ../strutils
|
||||||
|
import ../templates
|
||||||
|
|
||||||
|
import terminal
|
||||||
|
|
||||||
|
let e = newLineEditor()
|
||||||
|
e.prompt = ""
|
||||||
|
e.populateDefaults(enterSubmits = false)
|
||||||
|
let input = e.read()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import defaults
|
import ../plugin/defaults
|
||||||
import editor
|
import ../editor
|
||||||
import strutils
|
import ../strutils
|
||||||
import templates
|
import ../templates
|
||||||
|
|
||||||
var keep = true
|
var keep = true
|
||||||
|
|
||||||
|
@ -12,10 +12,8 @@ e.bindEvent(jeQuit):
|
||||||
|
|
||||||
e.prompt = "> "
|
e.prompt = "> "
|
||||||
e.populateDefaults()
|
e.populateDefaults()
|
||||||
|
|
||||||
while keep:
|
while keep:
|
||||||
let input = e.read()
|
let input = e.read()
|
||||||
echo "history index: " & $e.historyIndex
|
|
||||||
e.flush()
|
|
||||||
echo "history len: " & $e.history.len()
|
|
||||||
echo "output:<" & input.replace("\n", "\\n") & ">"
|
echo "output:<" & input.replace("\n", "\\n") & ">"
|
||||||
|
|
32
history.nim
32
history.nim
|
@ -1,32 +0,0 @@
|
||||||
# history.nim
|
|
||||||
|
|
||||||
import multiline
|
|
||||||
|
|
||||||
type HistoryElement* = ref object
|
|
||||||
original*: Multiline
|
|
||||||
current*: Multiline
|
|
||||||
|
|
||||||
type History* = ref object
|
|
||||||
elements: seq[HistoryElement]
|
|
||||||
index: int
|
|
||||||
lowestTouchedIndex: int
|
|
||||||
|
|
||||||
proc newHistoryElement*(og: Multiline): HistoryElement =
|
|
||||||
new(result)
|
|
||||||
result.original = og
|
|
||||||
new(result.current) # TODO deepcopy
|
|
||||||
|
|
||||||
proc delta*(h: History, amt: int): Multiline =
|
|
||||||
discard # move up/down in history and return reference to current
|
|
||||||
# also update lowest touched index
|
|
||||||
|
|
||||||
proc clean*(h: History) =
|
|
||||||
discard # restore originals to current
|
|
||||||
# from lowest touched index to the top
|
|
||||||
|
|
||||||
proc save*(h: History, path: string) =
|
|
||||||
discard # convert it to string, save
|
|
||||||
|
|
||||||
proc loadHistory*(path: string): History =
|
|
||||||
discard # create new history from file
|
|
||||||
|
|
16
line.nim
16
line.nim
|
@ -3,7 +3,7 @@
|
||||||
import strformat
|
import strformat
|
||||||
|
|
||||||
type
|
type
|
||||||
Line* = ref object
|
Line* = object
|
||||||
content: string
|
content: string
|
||||||
|
|
||||||
# getter
|
# getter
|
||||||
|
@ -16,9 +16,15 @@ proc content*(line: Line): string =
|
||||||
proc newLine*: Line =
|
proc newLine*: Line =
|
||||||
Line(content: "")
|
Line(content: "")
|
||||||
|
|
||||||
|
proc newLine*(str: string): Line =
|
||||||
|
Line(content: str)
|
||||||
|
|
||||||
|
proc copy*(l: Line): Line =
|
||||||
|
Line(content: l.content)
|
||||||
|
|
||||||
# methods
|
# methods
|
||||||
|
|
||||||
proc insert*(line: Line, str: string, pos: int) =
|
proc insert*(line: var Line, str: string, pos: int) =
|
||||||
if pos > line.content.high():
|
if pos > line.content.high():
|
||||||
line.content &= str
|
line.content &= str
|
||||||
elif pos == 0:
|
elif pos == 0:
|
||||||
|
@ -26,7 +32,7 @@ proc insert*(line: Line, str: string, pos: int) =
|
||||||
else:
|
else:
|
||||||
line.content = line.content[0..pos-1] & str & line.content[pos..line.content.high()]
|
line.content = line.content[0..pos-1] & str & line.content[pos..line.content.high()]
|
||||||
|
|
||||||
proc delete*(line: Line, start: int, finish: int) =
|
proc delete*(line: var Line, start: int, finish: int) =
|
||||||
if start > finish or start < 0 or finish > line.content.high():
|
if start > finish or start < 0 or finish > line.content.high():
|
||||||
raise newException(CatchableError, &"Invalid arguments for Line.delete: start {start}, finish {finish} for line of length {line.content.len()}")
|
raise newException(CatchableError, &"Invalid arguments for Line.delete: start {start}, finish {finish} for line of length {line.content.len()}")
|
||||||
var result = ""
|
var result = ""
|
||||||
|
@ -36,7 +42,7 @@ proc delete*(line: Line, start: int, finish: int) =
|
||||||
result &= line.content[finish+1..line.content.high()]
|
result &= line.content[finish+1..line.content.high()]
|
||||||
line.content = result
|
line.content = result
|
||||||
|
|
||||||
proc range*(line: Line, start: int, finish: int): string =
|
proc range*(line: var Line, start: int, finish: int): string =
|
||||||
if start > finish or start < 0 or finish > line.content.high():
|
if start > finish or start < 0 or finish > line.content.high():
|
||||||
raise newException(CatchableError, &"Invalid arguments for Line.range: start {start}, finish {finish} for line of length {line.content.len()}")
|
raise newException(CatchableError, &"Invalid arguments for Line.range: start {start}, finish {finish} for line of length {line.content.len()}")
|
||||||
result = line.content[start..finish]
|
result = line.content[start..finish]
|
||||||
|
@ -47,5 +53,5 @@ proc len*(line: Line): int =
|
||||||
proc high*(line: Line): int =
|
proc high*(line: Line): int =
|
||||||
line.content.high()
|
line.content.high()
|
||||||
|
|
||||||
proc clearLine*(line: Line) =
|
proc clearLine*(line: var Line) =
|
||||||
line.content = ""
|
line.content = ""
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import line
|
import line
|
||||||
|
|
||||||
|
import strutils
|
||||||
|
|
||||||
type
|
type
|
||||||
Multiline* = ref object
|
Multiline* = ref object
|
||||||
lines: seq[Line]
|
lines: seq[Line]
|
||||||
|
@ -24,6 +26,13 @@ proc newMultiline*: Multiline =
|
||||||
result.x = 0
|
result.x = 0
|
||||||
result.y = 0
|
result.y = 0
|
||||||
|
|
||||||
|
proc copy*(ml: Multiline): Multiline =
|
||||||
|
new(result)
|
||||||
|
for l in ml.lines:
|
||||||
|
result.lines.add(l.copy())
|
||||||
|
result.x = ml.x
|
||||||
|
result.y = ml.y
|
||||||
|
|
||||||
# methods
|
# methods
|
||||||
|
|
||||||
proc lineLen*(ml: Multiline): int =
|
proc lineLen*(ml: Multiline): int =
|
||||||
|
@ -139,8 +148,23 @@ proc getLine*(ml: Multiline, line: int = -1): string =
|
||||||
else:
|
else:
|
||||||
""
|
""
|
||||||
|
|
||||||
proc getContent*(ml: Multiline): string =
|
proc serialize*(ml: Multiline, sep: string = r"\n", replaceBS: bool = true): string =
|
||||||
|
# replaceBS = replace backslash
|
||||||
for line in ml.lines:
|
for line in ml.lines:
|
||||||
result &= line.content & "\n"
|
if replaceBS:
|
||||||
result[0..result.high()-1] # cut finishing newline
|
result &= line.content.replace(r"\", r"\\") & sep
|
||||||
|
else:
|
||||||
|
result &= line.content & sep
|
||||||
|
result[0..result.high() - sep.len()]
|
||||||
|
|
||||||
|
proc deserialize*(str: string, sep: string = r"\n"): Multiline =
|
||||||
|
result = newMultiline()
|
||||||
|
for line in str.split(sep):
|
||||||
|
result.lines.add(newLine(line.replace(r"\\", r"\")))
|
||||||
|
|
||||||
|
result.y = result.high()
|
||||||
|
result.x = result.lineLen()
|
||||||
|
|
||||||
|
proc getContent*(ml: Multiline): string =
|
||||||
|
ml.serialize(sep = "\n", replaceBS = false)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import ../editor
|
||||||
|
import ../keycodes
|
||||||
|
import ../multiline
|
||||||
|
import ../event
|
||||||
|
import ../templates
|
||||||
|
|
||||||
|
import tables
|
||||||
|
|
||||||
|
proc bindInput*(editor: LineEditor) =
|
||||||
|
editor.bindEvent(jeKeypress):
|
||||||
|
if editor.lastKeystroke > 31 and editor.lastKeystroke < 127:
|
||||||
|
let ch = char(editor.lastKeystroke)
|
||||||
|
editor.content.insert($ch)
|
||||||
|
|
||||||
|
proc bindTerminate*(editor: LineEditor) =
|
||||||
|
editor.bindKey("ctrl+c"):
|
||||||
|
editor.finish()
|
||||||
|
|
||||||
|
editor.bindKey("ctrl+d"):
|
||||||
|
if editor.content.getContent() == "":
|
||||||
|
editor.finish()
|
||||||
|
|
||||||
|
|
||||||
|
proc populateDefaults*(editor: LineEditor, enterSubmits = true, shiftForVerticalMove = true) =
|
||||||
|
editor.bindInput()
|
||||||
|
editor.bindTerminate()
|
||||||
|
editor.bindKey("left"):
|
||||||
|
editor.content.left()
|
||||||
|
editor.bindKey("right"):
|
||||||
|
editor.content.right()
|
||||||
|
if shiftForVerticalMove:
|
||||||
|
editor.bindKey("shiftup"):
|
||||||
|
editor.content.up()
|
||||||
|
editor.bindKey("shiftdown"):
|
||||||
|
editor.content.down()
|
||||||
|
editor.bindKey("ctrlpageup"):
|
||||||
|
editor.content.vhome()
|
||||||
|
editor.bindKey("ctrlpagedown"):
|
||||||
|
editor.content.vend()
|
||||||
|
else:
|
||||||
|
editor.bindKey("up"):
|
||||||
|
editor.content.up()
|
||||||
|
editor.bindKey("down"):
|
||||||
|
editor.content.down()
|
||||||
|
editor.bindKey("pageup"):
|
||||||
|
editor.content.vhome()
|
||||||
|
editor.bindKey("pagedown"):
|
||||||
|
editor.content.vend()
|
||||||
|
editor.bindKey("home"):
|
||||||
|
editor.content.home()
|
||||||
|
editor.bindKey("end"):
|
||||||
|
editor.content.`end`()
|
||||||
|
editor.bindKey("backspace"):
|
||||||
|
editor.content.backspace()
|
||||||
|
editor.bindKey("delete"):
|
||||||
|
editor.content.delete()
|
||||||
|
if enterSubmits:
|
||||||
|
editor.bindKey("ctrldown"):
|
||||||
|
editor.content.enter()
|
||||||
|
editor.bindKey("enter"):
|
||||||
|
editor.finish()
|
||||||
|
else:
|
||||||
|
editor.bindKey("enter"):
|
||||||
|
editor.content.enter()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import history
|
||||||
|
import ../editor
|
||||||
|
import ../multiline
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
# history.nim
|
||||||
|
|
||||||
|
import ../multiline
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
type HistoryElement* = ref object
|
||||||
|
original*: Multiline
|
||||||
|
current*: Multiline
|
||||||
|
temp: bool
|
||||||
|
|
||||||
|
type History* = ref object
|
||||||
|
elements: seq[HistoryElement]
|
||||||
|
index: int
|
||||||
|
lowestTouchedIndex: int
|
||||||
|
|
||||||
|
proc newHistory*: History =
|
||||||
|
new(result)
|
||||||
|
result.index = 0
|
||||||
|
result.lowestTouchedIndex = 0
|
||||||
|
result.elements = @[]
|
||||||
|
|
||||||
|
proc delta*(h: History, amt: int): Multiline =
|
||||||
|
# move up/down in history and return reference to current
|
||||||
|
# also update lowest touched index
|
||||||
|
if h.index + amt <= 0:
|
||||||
|
h.index = 0
|
||||||
|
elif h.index + amt >= h.elements.high():
|
||||||
|
h.index = h.elements.high()
|
||||||
|
else:
|
||||||
|
h.index += amt
|
||||||
|
|
||||||
|
if h.lowestTouchedIndex > h.index:
|
||||||
|
h.lowestTouchedIndex = h.index
|
||||||
|
|
||||||
|
h.elements[h.index]
|
||||||
|
|
||||||
|
proc clean*(h: History) =
|
||||||
|
# restore originals to current
|
||||||
|
# from lowest touched index to the top
|
||||||
|
|
||||||
|
if h.lowestTouchedIndex <= h.elements.high():
|
||||||
|
for i in countup(h.lowestTouchedIndex, h.elements.high()):
|
||||||
|
if h.elements[i].temp:
|
||||||
|
h.elements.delete(i)
|
||||||
|
else:
|
||||||
|
h.elements[i].current = h.elements[i].original.copy()
|
||||||
|
|
||||||
|
h.lowestTouchedIndex = h.elements.len()
|
||||||
|
|
||||||
|
proc newEntry*(h: History, ml: Multiline, temp: bool = false) =
|
||||||
|
h.elements.add(History(original: ml, current: ml.copy(), temp: temp))
|
||||||
|
|
||||||
|
proc save*(h: History, path: string) =
|
||||||
|
# discards currents and temps, only saves originals
|
||||||
|
if dirExists(path):
|
||||||
|
raise newException(CatchableError, "Attempt to save history to " & path & ", but a directory at that path exists")
|
||||||
|
|
||||||
|
let file = open(path, fmWrite)
|
||||||
|
|
||||||
|
for el in h.elements:
|
||||||
|
if el.temp:
|
||||||
|
continue
|
||||||
|
file.writeLine(el.original.serialize())
|
||||||
|
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
proc loadHistory*(path: string): History =
|
||||||
|
if not fileExists(path):
|
||||||
|
raise newException(CatchableError, "Attempt to read history from a non-existant file")
|
||||||
|
let file = open(path, fmRead)
|
||||||
|
|
||||||
|
let h = newHistory()
|
||||||
|
var line: string
|
||||||
|
while readLine(file, line):
|
||||||
|
h.newEntry(line.deserialize())
|
||||||
|
|
||||||
|
file.close()
|
||||||
|
return h
|
||||||
|
|
Loading…
Reference in New Issue