Split into multiple files
This commit is contained in:
parent
293e8cca85
commit
cf629494a4
9 changed files with 900 additions and 888 deletions
886
nimgen.nim
886
nimgen.nim
|
|
@ -1,886 +0,0 @@
|
|||
import os, ospaths, osproc, parsecfg, pegs, regex, ropes, sequtils, streams, strutils, tables
|
||||
|
||||
const
|
||||
cCompilerEnv = "CC"
|
||||
cppCompilerEnv = "CPP"
|
||||
defaultCCompiler = "gcc"
|
||||
defaultCppCompiler = "g++"
|
||||
|
||||
var
|
||||
gDoneRecursive: seq[string] = @[]
|
||||
gDoneInline: seq[string] = @[]
|
||||
|
||||
gProjectDir = ""
|
||||
gConfig: Config
|
||||
gFilter = ""
|
||||
gQuotes = true
|
||||
gCppCompiler = getEnv(cppCompilerEnv, defaultCCompiler)
|
||||
gCCompiler = getEnv(cCompilerEnv, defaultCppCompiler)
|
||||
gOutput = ""
|
||||
gIncludes: seq[string] = @[]
|
||||
gExcludes: seq[string] = @[]
|
||||
gRenames = initTable[string, string]()
|
||||
gWildcards = newConfig()
|
||||
|
||||
type
|
||||
c2nimConfigObj = object
|
||||
flags, ppflags: string
|
||||
recurse, inline, preprocess, ctags, defines: bool
|
||||
dynlib, compile, pragma: seq[string]
|
||||
|
||||
const DOC = """
|
||||
Nimgen is a helper for c2nim to simpilfy and automate the wrapping of C libraries
|
||||
|
||||
Usage:
|
||||
nimgen [options] <file.cfg>...
|
||||
|
||||
Options:
|
||||
-f delete all artifacts and regenerate
|
||||
"""
|
||||
|
||||
# ###
|
||||
# Helpers
|
||||
|
||||
proc addEnv(str: string): string =
|
||||
var newStr = str
|
||||
for pair in envPairs():
|
||||
try:
|
||||
newStr = newStr % [pair.key, pair.value.string]
|
||||
except ValueError:
|
||||
# Ignore if there are no values to replace. We
|
||||
# want to continue anyway
|
||||
discard
|
||||
|
||||
try:
|
||||
newStr = newStr % ["output", gOutput]
|
||||
except ValueError:
|
||||
# Ignore if there are no values to replace. We
|
||||
# want to continue anyway
|
||||
discard
|
||||
|
||||
# if there are still format args, print a warning
|
||||
if newStr.contains("${"):
|
||||
echo "WARNING: \"", newStr, "\" still contains an uninterpolated value!"
|
||||
|
||||
return newStr
|
||||
|
||||
proc `[]`(table: OrderedTableRef[string, string], key: string): string =
|
||||
## Gets table values with env vars inserted
|
||||
tables.`[]`(table, key).addEnv
|
||||
|
||||
proc execProc(cmd: string): string =
|
||||
result = ""
|
||||
var
|
||||
p = startProcess(cmd, options = {poStdErrToStdOut, poUsePath, poEvalCommand})
|
||||
|
||||
outp = outputStream(p)
|
||||
line = newStringOfCap(120).TaintedString
|
||||
|
||||
while true:
|
||||
if outp.readLine(line):
|
||||
result.add(line)
|
||||
result.add("\n")
|
||||
elif not running(p): break
|
||||
|
||||
var x = p.peekExitCode()
|
||||
if x != 0:
|
||||
echo "Command failed: " & $x
|
||||
echo cmd
|
||||
echo result
|
||||
quit(1)
|
||||
|
||||
proc extractZip(zipfile: string) =
|
||||
var cmd = "unzip -o $#"
|
||||
if defined(Windows):
|
||||
cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " &
|
||||
"'System.IO.Compression.FileSystem'; " &
|
||||
"[IO.Compression.ZipFile]::ExtractToDirectory('$#', '.'); }\""
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
echo "Extracting " & zipfile
|
||||
discard execProc(cmd % zipfile)
|
||||
|
||||
proc downloadUrl(url: string) =
|
||||
let
|
||||
file = url.extractFilename()
|
||||
ext = file.splitFile().ext.toLowerAscii()
|
||||
|
||||
var cmd = "curl $# -o $#"
|
||||
if defined(Windows):
|
||||
cmd = "powershell wget $# -OutFile $#"
|
||||
|
||||
if not (ext == ".zip" and fileExists(gOutput/file)):
|
||||
echo "Downloading " & file
|
||||
discard execProc(cmd % [url, gOutput/file])
|
||||
|
||||
if ext == ".zip":
|
||||
extractZip(file)
|
||||
|
||||
proc gitReset() =
|
||||
echo "Resetting Git repo"
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
discard execProc("git reset --hard HEAD")
|
||||
|
||||
proc gitCheckout(filename: string) {.used.} =
|
||||
echo "Resetting file: $#" % [filename]
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
let adjustedFile = filename.replace(gOutput & $DirSep, "")
|
||||
|
||||
discard execProc("git checkout $#" % [adjustedFile])
|
||||
|
||||
proc gitRemotePull(url: string, pull=true) =
|
||||
if dirExists(gOutput/".git"):
|
||||
if pull:
|
||||
gitReset()
|
||||
return
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
echo "Setting up Git repo"
|
||||
discard execProc("git init .")
|
||||
discard execProc("git remote add origin " & url)
|
||||
|
||||
if pull:
|
||||
echo "Checking out artifacts"
|
||||
discard execProc("git pull --depth=1 origin master")
|
||||
|
||||
proc gitSparseCheckout(plist: string) =
|
||||
let sparsefile = ".git/info/sparse-checkout"
|
||||
if fileExists(gOutput/sparsefile):
|
||||
gitReset()
|
||||
return
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
discard execProc("git config core.sparsecheckout true")
|
||||
writeFile(sparsefile, plist)
|
||||
|
||||
echo "Checking out artifacts"
|
||||
discard execProc("git pull --depth=1 origin master")
|
||||
|
||||
proc doCopy(flist: string) =
|
||||
for pair in flist.split(","):
|
||||
let spl = pair.split("=")
|
||||
if spl.len() != 2:
|
||||
echo "Bad copy syntax: " & flist
|
||||
quit(1)
|
||||
|
||||
let
|
||||
lfile = spl[0].strip()
|
||||
rfile = spl[1].strip()
|
||||
|
||||
copyFile(lfile, rfile)
|
||||
echo "Copied $# to $#" % [lfile, rfile]
|
||||
|
||||
proc getKey(ukey: string): tuple[key: string, val: bool] =
|
||||
var kv = ukey.replace(re"\..*", "").split("-", 1)
|
||||
if kv.len() == 1:
|
||||
kv.add("")
|
||||
|
||||
if (kv[1] == "") or
|
||||
(kv[1] == "win" and defined(Windows)) or
|
||||
(kv[1] == "lin" and defined(Linux)) or
|
||||
(kv[1] == "osx" and defined(MacOSX)):
|
||||
return (kv[0], true)
|
||||
|
||||
return (kv[0], false)
|
||||
|
||||
# ###
|
||||
# File loction
|
||||
|
||||
proc getNimout(file: string, rename=true): string =
|
||||
result = file.splitFile().name.replace(re"[\-\.]", "_") & ".nim"
|
||||
if gOutput != "":
|
||||
result = gOutput/result
|
||||
|
||||
if not rename:
|
||||
return
|
||||
|
||||
if gRenames.hasKey(file):
|
||||
result = gRenames[file]
|
||||
|
||||
if not dirExists(parentDir(result)):
|
||||
createDir(parentDir(result))
|
||||
|
||||
proc exclude(file: string): bool =
|
||||
for excl in gExcludes:
|
||||
if excl in file:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc search(file: string): string =
|
||||
if exclude(file):
|
||||
return ""
|
||||
|
||||
result = file
|
||||
if file.splitFile().ext == ".nim":
|
||||
result = getNimout(file)
|
||||
elif not fileExists(result) and not dirExists(result):
|
||||
var found = false
|
||||
for inc in gIncludes:
|
||||
result = inc/file
|
||||
if fileExists(result) or dirExists(result):
|
||||
found = true
|
||||
break
|
||||
if not found:
|
||||
echo "File doesn't exist: " & file
|
||||
quit(1)
|
||||
|
||||
# Only keep relative directory
|
||||
return result.multiReplace([("\\", $DirSep), ("//", $DirSep), (gProjectDir & $DirSep, "")])
|
||||
|
||||
# ###
|
||||
# Loading / unloading
|
||||
|
||||
proc openRetry(file: string, mode: FileMode = fmRead): File =
|
||||
while true:
|
||||
try:
|
||||
result = open(file, mode)
|
||||
break
|
||||
except IOError:
|
||||
sleep(100)
|
||||
|
||||
template withFile(file: string, body: untyped): untyped =
|
||||
if fileExists(file):
|
||||
var f = openRetry(file)
|
||||
|
||||
var contentOrig = f.readAll()
|
||||
f.close()
|
||||
var content {.inject.} = contentOrig
|
||||
|
||||
body
|
||||
|
||||
if content != contentOrig:
|
||||
f = openRetry(file, fmWrite)
|
||||
write(f, content)
|
||||
f.close()
|
||||
else:
|
||||
echo "Missing file " & file
|
||||
|
||||
# ###
|
||||
# Manipulating content
|
||||
|
||||
proc prepend(file: string, data: string, search="") =
|
||||
withFile(file):
|
||||
if search == "":
|
||||
content = data & content
|
||||
else:
|
||||
let idx = content.find(search)
|
||||
if idx != -1:
|
||||
content = content[0..<idx] & data & content[idx..<content.len()]
|
||||
|
||||
proc pipe(file: string, command: string) =
|
||||
let cmd = command % ["file", file]
|
||||
let commandResult = execProc(cmd).strip()
|
||||
if commandResult != "":
|
||||
withFile(file):
|
||||
content = commandResult
|
||||
|
||||
proc append(file: string, data: string, search="") =
|
||||
withFile(file):
|
||||
if search == "":
|
||||
content &= data
|
||||
else:
|
||||
let idx = content.find(search)
|
||||
let idy = idx + search.len()
|
||||
if idx != -1:
|
||||
content = content[0..<idy] & data & content[idy..<content.len()]
|
||||
|
||||
proc freplace(file: string, pattern: string, repl="") =
|
||||
withFile(file):
|
||||
if pattern in content:
|
||||
content = content.replace(pattern, repl)
|
||||
|
||||
proc freplace(file: string, pattern: Regex, repl="") =
|
||||
withFile(file):
|
||||
var m: RegexMatch
|
||||
if content.find(pattern, m):
|
||||
if "$#" in repl:
|
||||
content = content.replace(pattern,
|
||||
proc (m: RegexMatch, s: string): string = repl % s[m.group(0)[0]])
|
||||
else:
|
||||
content = content.replace(pattern, repl)
|
||||
|
||||
proc comment(file: string, pattern: string, numlines: string) =
|
||||
let
|
||||
ext = file.splitFile().ext.toLowerAscii()
|
||||
cmtchar = if ext == ".nim": "#" else: "//"
|
||||
|
||||
withFile(file):
|
||||
var
|
||||
idx = content.find(pattern)
|
||||
num = 0
|
||||
|
||||
try:
|
||||
num = numlines.parseInt()
|
||||
except ValueError:
|
||||
echo "Bad comment value, should be integer: " & numlines
|
||||
if idx != -1:
|
||||
for i in 0 .. num-1:
|
||||
if idx >= content.len():
|
||||
break
|
||||
content = content[0..<idx] & cmtchar & content[idx..<content.len()]
|
||||
while idx < content.len():
|
||||
idx += 1
|
||||
if content[idx] == '\L':
|
||||
idx += 1
|
||||
break
|
||||
|
||||
proc removeStatic(filename: string) =
|
||||
## Replace static function bodies with a semicolon and commented
|
||||
## out body
|
||||
withFile(filename):
|
||||
content = content.replace(
|
||||
re"(?m)(static inline.*?\))(\s*\{(\s*?.*?$)*[\n\r]\})",
|
||||
proc (m: RegexMatch, s: string): string =
|
||||
let funcDecl = s[m.group(0)[0]]
|
||||
let body = s[m.group(1)[0]].strip()
|
||||
result = ""
|
||||
|
||||
result.add("$#;" % [funcDecl])
|
||||
result.add(body.replace(re"(?m)^", "//"))
|
||||
)
|
||||
|
||||
proc reAddStatic(filename: string) =
|
||||
## Uncomment out the body and remove the semicolon. Undoes
|
||||
## removeStatic
|
||||
withFile(filename):
|
||||
content = content.replace(
|
||||
re"(?m)(static inline.*?\));(\/\/\s*\{(\s*?.*?$)*[\n\r]\/\/\})",
|
||||
proc (m: RegexMatch, s: string): string =
|
||||
let funcDecl = s[m.group(0)[0]]
|
||||
let body = s[m.group(1)[0]].strip()
|
||||
result = ""
|
||||
|
||||
result.add("$# " % [funcDecl])
|
||||
result.add(body.replace(re"(?m)^\/\/", ""))
|
||||
)
|
||||
|
||||
proc rename(file: string, renfile: string) =
|
||||
if file.splitFile().ext == ".nim":
|
||||
return
|
||||
|
||||
var
|
||||
nimout = getNimout(file, false)
|
||||
newname = renfile.replace("$nimout", extractFilename(nimout))
|
||||
|
||||
if newname =~ peg"(!\$.)*{'$replace'\s*'('\s*{(!\)\S)+}')'}":
|
||||
var final = nimout.extractFilename()
|
||||
for entry in matches[1].split(","):
|
||||
let spl = entry.split("=")
|
||||
if spl.len() != 2:
|
||||
echo "Bad replace syntax: " & renfile
|
||||
quit(1)
|
||||
|
||||
let
|
||||
srch = spl[0].strip()
|
||||
repl = spl[1].strip()
|
||||
|
||||
final = final.replace(srch, repl)
|
||||
newname = newname.replace(matches[0], final)
|
||||
|
||||
gRenames[file] = gOutput/newname
|
||||
|
||||
proc compile(dir="", file=""): string =
|
||||
proc fcompile(file: string): string =
|
||||
return "{.compile: \"$#\".}" % file.replace("\\", "/")
|
||||
|
||||
var data = ""
|
||||
if dir != "" and dirExists(dir):
|
||||
for f in walkFiles(dir / "*.c"):
|
||||
data &= fcompile(f) & "\n"
|
||||
|
||||
if file != "" and fileExists(file):
|
||||
data &= fcompile(file) & "\n"
|
||||
|
||||
return data
|
||||
|
||||
proc fixFuncProtos(file: string) =
|
||||
withFile(file):
|
||||
content = content.replace(re"(?m)(^.*?)[ ]*\(\*(.*?)\((.*?)\)\)[ \r\n]*\((.*?[\r\n]*.*?)\);",
|
||||
proc (m: RegexMatch, s: string): string =
|
||||
(("typedef $# (*type_$#)($#);\n" % [s[m.group(0)[0]], s[m.group(1)[0]], s[m.group(3)[0]]]) &
|
||||
("type_$# $#($#);" % [s[m.group(1)[0]], s[m.group(1)[0]], s[m.group(2)[0]]]))
|
||||
)
|
||||
|
||||
# ###
|
||||
# Convert to Nim
|
||||
|
||||
proc getIncls(file: string, inline=false): seq[string] =
|
||||
result = @[]
|
||||
if inline and file in gDoneInline:
|
||||
return
|
||||
|
||||
let curPath = splitFile(expandFileName(file)).dir
|
||||
withFile(file):
|
||||
for f in content.findAll(re"(?m)^\s*#\s*include\s+(.*?)$"):
|
||||
var inc = content[f.group(0)[0]].strip()
|
||||
if ((gQuotes and inc.contains("\"")) or (gFilter != "" and gFilter in inc)) and (not exclude(inc)):
|
||||
let addInc = inc.replace(re"""[<>"]""", "").replace(re"\/[\*\/].*$", "").strip()
|
||||
try:
|
||||
# Try searching for a local library. expandFilename will throw
|
||||
# OSError if the file does not exist
|
||||
let
|
||||
finc = expandFileName(curPath / addInc)
|
||||
fname = finc.replace(curPath & $DirSep, "")
|
||||
|
||||
if fname.len() > 0:
|
||||
# only add if the file is non-empty
|
||||
result.add(fname.search())
|
||||
except OSError:
|
||||
# If it's a system library
|
||||
result.add(addInc)
|
||||
|
||||
result = result.deduplicate()
|
||||
|
||||
gDoneInline.add(file)
|
||||
|
||||
if inline:
|
||||
var sres = newSeq[string]()
|
||||
for incl in result:
|
||||
let sincl = search(incl)
|
||||
if sincl == "":
|
||||
continue
|
||||
|
||||
sres.add(getIncls(sincl, inline))
|
||||
result.add(sres)
|
||||
|
||||
result = result.deduplicate()
|
||||
|
||||
proc getDefines(file: string, inline=false): string =
|
||||
result = ""
|
||||
if inline:
|
||||
var incls = getIncls(file, inline)
|
||||
for incl in incls:
|
||||
let sincl = search(incl)
|
||||
if sincl != "":
|
||||
echo "Inlining " & sincl
|
||||
result &= getDefines(sincl)
|
||||
withFile(file):
|
||||
for def in content.findAll(re"(?m)^(\s*#\s*define\s+[\w\d_]+\s+[\d\-.xf]+)(?:\r|//|/*).*?$"):
|
||||
result &= content[def.group(0)[0]] & "\n"
|
||||
|
||||
proc runPreprocess(file, ppflags, flags: string, inline: bool): string =
|
||||
var
|
||||
pproc = if flags.contains("cpp"): gCppCompiler else: gCCompiler
|
||||
cmd = "$# -E $# $#" % [pproc, ppflags, file]
|
||||
|
||||
for inc in gIncludes:
|
||||
cmd &= " -I " & inc
|
||||
|
||||
# Run preprocessor
|
||||
var data = execProc(cmd)
|
||||
|
||||
# Include content only from file
|
||||
var
|
||||
rdata: Rope
|
||||
start = false
|
||||
sfile = file.replace("\\", "/")
|
||||
|
||||
if inline:
|
||||
sfile = sfile.parentDir()
|
||||
for line in data.splitLines():
|
||||
if line.strip() != "":
|
||||
if line[0] == '#' and not line.contains("#pragma"):
|
||||
start = false
|
||||
if sfile in line.replace("\\", "/").replace("//", "/"):
|
||||
start = true
|
||||
if not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
|
||||
start = true
|
||||
else:
|
||||
if start:
|
||||
rdata.add(
|
||||
line.replace("_Noreturn", "")
|
||||
.replace("(())", "")
|
||||
.replace("WINAPI", "")
|
||||
.replace("__attribute__", "")
|
||||
.replace("extern \"C\"", "")
|
||||
.replace(re"\(\([_a-z]+?\)\)", "")
|
||||
.replace(re"\(\(__format__[\s]*\(__[gnu_]*printf__, [\d]+, [\d]+\)\)\);", ";") & "\n"
|
||||
)
|
||||
return $rdata
|
||||
|
||||
proc runCtags(file: string): string =
|
||||
var
|
||||
cmd = "ctags -o - --fields=+S+K --c-kinds=+p --file-scope=no " & file
|
||||
fps = execProc(cmd)
|
||||
fdata = ""
|
||||
|
||||
for line in fps.splitLines():
|
||||
var spl = line.split(re"\t")
|
||||
if spl.len() > 4:
|
||||
if spl[0] != "main" and spl[3] != "member":
|
||||
var fn = ""
|
||||
var match: RegexMatch
|
||||
if spl[2].find(re"/\^(.*?)\(", match):
|
||||
fn = spl[2][match.group(0)[0]]
|
||||
fn &= spl[4].replace("signature:", "") & ";"
|
||||
fdata &= fn & "\n"
|
||||
|
||||
return fdata
|
||||
|
||||
proc runFile(file: string, cfgin: OrderedTableRef)
|
||||
|
||||
template relativePath(path: untyped): untyped =
|
||||
path.multiReplace([(gOutput, ""), ("\\", "/"), ("//", "/")])
|
||||
|
||||
proc c2nim(fl, outfile: string, c2nimConfig: c2nimConfigObj) =
|
||||
var file = search(fl)
|
||||
if file == "":
|
||||
return
|
||||
|
||||
if file in gDoneRecursive:
|
||||
return
|
||||
|
||||
echo "Processing $# => $#" % [file, outfile]
|
||||
gDoneRecursive.add(file)
|
||||
|
||||
fixFuncProtos(file)
|
||||
|
||||
var incout = ""
|
||||
if c2nimConfig.recurse:
|
||||
var
|
||||
incls = getIncls(file)
|
||||
cfg = newOrderedTable[string, string]()
|
||||
|
||||
for name, value in c2nimConfig.fieldPairs:
|
||||
when value is string:
|
||||
cfg[name] = value
|
||||
when value is bool:
|
||||
cfg[name] = $value
|
||||
|
||||
for i in c2nimConfig.dynlib:
|
||||
cfg["dynlib." & i] = i
|
||||
|
||||
for inc in incls:
|
||||
runFile(inc, cfg)
|
||||
incout &= "import $#\n" % inc.search().getNimout()[0 .. ^5]
|
||||
|
||||
var cfile = file
|
||||
if c2nimConfig.preprocess:
|
||||
cfile = "temp-$#.c" % [outfile.extractFilename()]
|
||||
writeFile(cfile, runPreprocess(file, c2nimConfig.ppflags, c2nimConfig.flags, c2nimConfig.inline))
|
||||
elif c2nimConfig.ctags:
|
||||
cfile = "temp-$#.c" % [outfile.extractFilename()]
|
||||
writeFile(cfile, runCtags(file))
|
||||
|
||||
if c2nimConfig.defines and (c2nimConfig.preprocess or c2nimConfig.ctags):
|
||||
prepend(cfile, getDefines(file, c2nimConfig.inline))
|
||||
|
||||
var
|
||||
extflags = ""
|
||||
passC = ""
|
||||
outlib = ""
|
||||
outpragma = ""
|
||||
|
||||
passC = "import ospaths, strutils\n"
|
||||
|
||||
for inc in gIncludes:
|
||||
if inc.isAbsolute():
|
||||
passC &= ("""{.passC: "-I\"$#\"".}""" % [inc]) & "\n"
|
||||
else:
|
||||
passC &= (
|
||||
"""{.passC: "-I\"" & currentSourcePath().splitPath().head & "$#\"".}""" %
|
||||
inc.relativePath()
|
||||
) & "\n"
|
||||
|
||||
for prag in c2nimConfig.pragma:
|
||||
outpragma &= "{." & prag & ".}\n"
|
||||
|
||||
let fname = file.splitFile().name.replace(re"[\.\-]", "_")
|
||||
|
||||
if c2nimConfig.dynlib.len() != 0:
|
||||
let
|
||||
win = "when defined(Windows):\n"
|
||||
lin = "when defined(Linux):\n"
|
||||
osx = "when defined(MacOSX):\n"
|
||||
|
||||
var winlib, linlib, osxlib: string = ""
|
||||
for dl in c2nimConfig.dynlib:
|
||||
let
|
||||
lib = " const dynlib$# = \"$#\"\n" % [fname, dl]
|
||||
ext = dl.splitFile().ext
|
||||
|
||||
if ext == ".dll":
|
||||
winlib &= lib
|
||||
elif ext == ".so":
|
||||
linlib &= lib
|
||||
elif ext == ".dylib":
|
||||
osxlib &= lib
|
||||
|
||||
if winlib != "":
|
||||
outlib &= win & winlib & "\n"
|
||||
if linlib != "":
|
||||
outlib &= lin & linlib & "\n"
|
||||
if osxlib != "":
|
||||
outlib &= osx & osxlib & "\n"
|
||||
|
||||
if outlib != "":
|
||||
extflags &= " --dynlib:dynlib$#" % fname
|
||||
else:
|
||||
if file.isAbsolute():
|
||||
passC &= "const header$# = \"$#\"\n" % [fname, file]
|
||||
else:
|
||||
passC &= "const header$# = currentSourcePath().splitPath().head & \"$#\"\n" %
|
||||
[fname, file.relativePath()]
|
||||
extflags = "--header:header$#" % fname
|
||||
|
||||
# Run c2nim on generated file
|
||||
var cmd = "c2nim $# $# --out:$# $#" % [c2nimConfig.flags, extflags, outfile, cfile]
|
||||
when defined(windows):
|
||||
cmd = "cmd /c " & cmd
|
||||
discard execProc(cmd)
|
||||
|
||||
if c2nimConfig.preprocess or c2nimConfig.ctags:
|
||||
try:
|
||||
removeFile(cfile)
|
||||
except:
|
||||
discard
|
||||
|
||||
# Import nim modules
|
||||
if c2nimConfig.recurse:
|
||||
prepend(outfile, incout)
|
||||
|
||||
# Nim doesn't like {.cdecl.} for type proc()
|
||||
freplace(outfile, re"(?m)(.*? = proc.*?)\{.cdecl.\}", "$#")
|
||||
freplace(outfile, " {.cdecl.})", ")")
|
||||
|
||||
# Include {.compile.} directives
|
||||
for cpl in c2nimConfig.compile:
|
||||
let fcpl = search(cpl)
|
||||
if getFileInfo(fcpl).kind == pcFile:
|
||||
prepend(outfile, compile(file=fcpl))
|
||||
else:
|
||||
prepend(outfile, compile(dir=fcpl))
|
||||
|
||||
# Add any pragmas
|
||||
if outpragma != "":
|
||||
prepend(outfile, outpragma)
|
||||
|
||||
# Add header file and include paths
|
||||
if passC != "":
|
||||
prepend(outfile, passC)
|
||||
|
||||
# Add dynamic library
|
||||
if outlib != "":
|
||||
prepend(outfile, outlib)
|
||||
|
||||
# ###
|
||||
# Processor
|
||||
|
||||
proc runFile(file: string, cfgin: OrderedTableRef) =
|
||||
var
|
||||
cfg = cfgin
|
||||
sfile = search(file)
|
||||
|
||||
for pattern in gWildcards.keys():
|
||||
var match: RegexMatch
|
||||
let pat = pattern.replace(".", "\\.").replace("*", ".*").replace("?", ".?")
|
||||
if file.find(toPattern(pat), match):
|
||||
echo "Appending " & file & " " & pattern
|
||||
for key in gWildcards[pattern].keys():
|
||||
cfg[key & "." & pattern] = gWildcards[pattern][key]
|
||||
|
||||
var
|
||||
srch = ""
|
||||
|
||||
c2nimConfig = c2nimConfigObj(
|
||||
flags: "--stdcall", ppflags: "",
|
||||
recurse: false, inline: false, preprocess: false, ctags: false, defines: false,
|
||||
dynlib: @[], compile: @[], pragma: @[]
|
||||
)
|
||||
|
||||
for act in cfg.keys():
|
||||
let (action, val) = getKey(act)
|
||||
if val == true:
|
||||
if action == "create":
|
||||
createDir(file.splitPath().head)
|
||||
writeFile(file, cfg[act])
|
||||
elif action in @["prepend", "append", "replace", "comment",
|
||||
"rename", "compile", "dynlib", "pragma",
|
||||
"pipe"] and sfile != "":
|
||||
if action == "prepend":
|
||||
if srch != "":
|
||||
prepend(sfile, cfg[act], cfg[srch])
|
||||
else:
|
||||
prepend(sfile, cfg[act])
|
||||
elif action == "append":
|
||||
if srch != "":
|
||||
append(sfile, cfg[act], cfg[srch])
|
||||
else:
|
||||
append(sfile, cfg[act])
|
||||
elif action == "replace":
|
||||
if srch != "":
|
||||
freplace(sfile, cfg[srch], cfg[act])
|
||||
elif action == "comment":
|
||||
if srch != "":
|
||||
comment(sfile, cfg[srch], cfg[act])
|
||||
elif action == "rename":
|
||||
rename(sfile, cfg[act])
|
||||
elif action == "compile":
|
||||
c2nimConfig.compile.add(cfg[act])
|
||||
elif action == "dynlib":
|
||||
c2nimConfig.dynlib.add(cfg[act])
|
||||
elif action == "pragma":
|
||||
c2nimConfig.pragma.add(cfg[act])
|
||||
elif action == "pipe":
|
||||
pipe(sfile, cfg[act])
|
||||
srch = ""
|
||||
elif action == "search":
|
||||
srch = act
|
||||
|
||||
if file.splitFile().ext != ".nim":
|
||||
var noprocess = false
|
||||
|
||||
for act in cfg.keys():
|
||||
let (action, val) = getKey(act)
|
||||
if val == true:
|
||||
if cfg[act] == "true":
|
||||
if action == "recurse":
|
||||
c2nimConfig.recurse = true
|
||||
elif action == "inline":
|
||||
c2nimConfig.inline = true
|
||||
elif action == "preprocess":
|
||||
c2nimConfig.preprocess = true
|
||||
elif action == "ctags":
|
||||
c2nimConfig.ctags = true
|
||||
elif action == "defines":
|
||||
c2nimConfig.defines = true
|
||||
elif action == "noprocess":
|
||||
noprocess = true
|
||||
elif action == "flags":
|
||||
c2nimConfig.flags = cfg[act]
|
||||
elif action == "ppflags":
|
||||
c2nimConfig.ppflags = cfg[act]
|
||||
|
||||
if c2nimConfig.recurse and c2nimConfig.inline:
|
||||
echo "Cannot use recurse and inline simultaneously"
|
||||
quit(1)
|
||||
|
||||
# Remove static inline function bodies
|
||||
removeStatic(sfile)
|
||||
|
||||
if not noprocess:
|
||||
c2nim(file, getNimout(sfile), c2nimConfig)
|
||||
|
||||
# Add them back for compilation
|
||||
reAddStatic(sfile)
|
||||
|
||||
proc runCfg(cfg: string) =
|
||||
if not fileExists(cfg):
|
||||
echo "Config doesn't exist: " & cfg
|
||||
quit(1)
|
||||
|
||||
gProjectDir = parentDir(cfg.expandFilename())
|
||||
|
||||
gConfig = loadConfig(cfg)
|
||||
|
||||
if gConfig.hasKey("n.global"):
|
||||
if gConfig["n.global"].hasKey("output"):
|
||||
gOutput = gConfig["n.global"]["output"]
|
||||
if dirExists(gOutput):
|
||||
if "-f" in commandLineParams():
|
||||
try:
|
||||
removeDir(gOutput)
|
||||
except OSError:
|
||||
echo "Directory in use: " & gOutput
|
||||
quit(1)
|
||||
else:
|
||||
for f in walkFiles(gOutput/"*.nim"):
|
||||
try:
|
||||
removeFile(f)
|
||||
except OSError:
|
||||
echo "Unable to delete: " & f
|
||||
quit(1)
|
||||
createDir(gOutput)
|
||||
|
||||
if gConfig["n.global"].hasKey("cpp_compiler"):
|
||||
gCppCompiler = gConfig["n.global"]["cpp_compiler"]
|
||||
else:
|
||||
# Reset on a per project basis
|
||||
gCppCompiler = getEnv(cppCompilerEnv, defaultCppCompiler)
|
||||
|
||||
if gConfig["n.global"].hasKey("c_compiler"):
|
||||
gCCompiler = gConfig["n.global"]["c_compiler"]
|
||||
else:
|
||||
# Reset on a per project basis
|
||||
gCCompiler = getEnv(cCompilerEnv, defaultCCompiler)
|
||||
|
||||
if gConfig["n.global"].hasKey("filter"):
|
||||
gFilter = gConfig["n.global"]["filter"]
|
||||
if gConfig["n.global"].hasKey("quotes"):
|
||||
if gConfig["n.global"]["quotes"] == "false":
|
||||
gQuotes = false
|
||||
|
||||
if gConfig.hasKey("n.include"):
|
||||
for inc in gConfig["n.include"].keys():
|
||||
gIncludes.add(inc.addEnv())
|
||||
|
||||
if gConfig.hasKey("n.exclude"):
|
||||
for excl in gConfig["n.exclude"].keys():
|
||||
gExcludes.add(excl.addEnv())
|
||||
|
||||
if gConfig.hasKey("n.prepare"):
|
||||
for prep in gConfig["n.prepare"].keys():
|
||||
let (key, val) = getKey(prep)
|
||||
if val == true:
|
||||
let prepVal = gConfig["n.prepare"][prep]
|
||||
if key == "download":
|
||||
downloadUrl(prepVal)
|
||||
elif key == "extract":
|
||||
extractZip(prepVal)
|
||||
elif key == "git":
|
||||
gitRemotePull(prepVal)
|
||||
elif key == "gitremote":
|
||||
gitRemotePull(prepVal, false)
|
||||
elif key == "gitsparse":
|
||||
gitSparseCheckout(prepVal)
|
||||
elif key == "execute":
|
||||
discard execProc(prepVal)
|
||||
elif key == "copy":
|
||||
doCopy(prepVal)
|
||||
|
||||
if gConfig.hasKey("n.wildcard"):
|
||||
var wildcard = ""
|
||||
for wild in gConfig["n.wildcard"].keys():
|
||||
let (key, val) = getKey(wild)
|
||||
if val == true:
|
||||
if key == "wildcard":
|
||||
wildcard = gConfig["n.wildcard"][wild]
|
||||
else:
|
||||
gWildcards.setSectionKey(wildcard, wild,
|
||||
gConfig["n.wildcard"][wild])
|
||||
|
||||
for file in gConfig.keys():
|
||||
if file in @["n.global", "n.include", "n.exclude",
|
||||
"n.prepare", "n.wildcard", "n.post"]:
|
||||
continue
|
||||
|
||||
runFile(file, gConfig[file])
|
||||
|
||||
if gConfig.hasKey("n.post"):
|
||||
for post in gConfig["n.post"].keys():
|
||||
let (key, val) = getKey(post)
|
||||
if val == true:
|
||||
let postVal = gConfig["n.post"][post]
|
||||
if key == "reset":
|
||||
gitReset()
|
||||
elif key == "execute":
|
||||
discard execProc(postVal)
|
||||
|
||||
# ###
|
||||
# Main loop
|
||||
|
||||
for i in commandLineParams():
|
||||
if i != "-f":
|
||||
runCfg(i)
|
||||
|
|
@ -5,13 +5,13 @@ author = "genotrance"
|
|||
description = "c2nim helper to simplify and automate the wrapping of C libraries"
|
||||
license = "MIT"
|
||||
|
||||
bin = @["nimgen"]
|
||||
srcDir = "src"
|
||||
skipDirs = @["tests"]
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 0.17.0", "c2nim >= 0.9.13", "regex >= 0.7.1"
|
||||
|
||||
bin = @["nimgen"]
|
||||
|
||||
task test, "Test nimgen":
|
||||
exec "nim e tests/nimgentest.nims"
|
||||
|
|
|
|||
7
src/nimgen.nim
Normal file
7
src/nimgen.nim
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import os
|
||||
|
||||
import nimgen/config
|
||||
|
||||
for i in commandLineParams():
|
||||
if i != "-f":
|
||||
runCfg(i)
|
||||
229
src/nimgen/config.nim
Normal file
229
src/nimgen/config.nim
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import os, parsecfg, regex, strutils, tables
|
||||
|
||||
import file, fileops, gencore, globals, prepare
|
||||
|
||||
proc `[]`*(table: OrderedTableRef[string, string], key: string): string =
|
||||
## Gets table values with env vars inserted
|
||||
tables.`[]`(table, key).addEnv
|
||||
|
||||
proc getKey(ukey: string): tuple[key: string, val: bool] =
|
||||
var kv = ukey.replace(re"\..*", "").split("-", 1)
|
||||
if kv.len() == 1:
|
||||
kv.add("")
|
||||
|
||||
if (kv[1] == "") or
|
||||
(kv[1] == "win" and defined(Windows)) or
|
||||
(kv[1] == "lin" and defined(Linux)) or
|
||||
(kv[1] == "osx" and defined(MacOSX)):
|
||||
return (kv[0], true)
|
||||
|
||||
return (kv[0], false)
|
||||
|
||||
proc runFile*(file: string, cfgin: OrderedTableRef) =
|
||||
var
|
||||
cfg = cfgin
|
||||
sfile = search(file)
|
||||
|
||||
for pattern in gWildcards.keys():
|
||||
var match: RegexMatch
|
||||
let pat = pattern.replace(".", "\\.").replace("*", ".*").replace("?", ".?")
|
||||
if file.find(toPattern(pat), match):
|
||||
echo "Appending " & file & " " & pattern
|
||||
for key in gWildcards[pattern].keys():
|
||||
cfg[key & "." & pattern] = gWildcards[pattern][key]
|
||||
|
||||
var
|
||||
srch = ""
|
||||
|
||||
c2nimConfig = c2nimConfigObj(
|
||||
flags: "--stdcall", ppflags: "",
|
||||
recurse: false, inline: false, preprocess: false, ctags: false, defines: false,
|
||||
dynlib: @[], compile: @[], pragma: @[]
|
||||
)
|
||||
|
||||
for act in cfg.keys():
|
||||
let (action, val) = getKey(act)
|
||||
if val == true:
|
||||
if action == "create":
|
||||
createDir(file.splitPath().head)
|
||||
writeFile(file, cfg[act])
|
||||
elif action in @["prepend", "append", "replace", "comment",
|
||||
"rename", "compile", "dynlib", "pragma",
|
||||
"pipe"] and sfile != "":
|
||||
if action == "prepend":
|
||||
if srch != "":
|
||||
prepend(sfile, cfg[act], cfg[srch])
|
||||
else:
|
||||
prepend(sfile, cfg[act])
|
||||
elif action == "append":
|
||||
if srch != "":
|
||||
append(sfile, cfg[act], cfg[srch])
|
||||
else:
|
||||
append(sfile, cfg[act])
|
||||
elif action == "replace":
|
||||
if srch != "":
|
||||
freplace(sfile, cfg[srch], cfg[act])
|
||||
elif action == "comment":
|
||||
if srch != "":
|
||||
comment(sfile, cfg[srch], cfg[act])
|
||||
elif action == "rename":
|
||||
rename(sfile, cfg[act])
|
||||
elif action == "compile":
|
||||
c2nimConfig.compile.add(cfg[act])
|
||||
elif action == "dynlib":
|
||||
c2nimConfig.dynlib.add(cfg[act])
|
||||
elif action == "pragma":
|
||||
c2nimConfig.pragma.add(cfg[act])
|
||||
elif action == "pipe":
|
||||
pipe(sfile, cfg[act])
|
||||
srch = ""
|
||||
elif action == "search":
|
||||
srch = act
|
||||
|
||||
if file.splitFile().ext != ".nim":
|
||||
var noprocess = false
|
||||
|
||||
for act in cfg.keys():
|
||||
let (action, val) = getKey(act)
|
||||
if val == true:
|
||||
if cfg[act] == "true":
|
||||
if action == "recurse":
|
||||
c2nimConfig.recurse = true
|
||||
elif action == "inline":
|
||||
c2nimConfig.inline = true
|
||||
elif action == "preprocess":
|
||||
c2nimConfig.preprocess = true
|
||||
elif action == "ctags":
|
||||
c2nimConfig.ctags = true
|
||||
elif action == "defines":
|
||||
c2nimConfig.defines = true
|
||||
elif action == "noprocess":
|
||||
noprocess = true
|
||||
elif action == "flags":
|
||||
c2nimConfig.flags = cfg[act]
|
||||
elif action == "ppflags":
|
||||
c2nimConfig.ppflags = cfg[act]
|
||||
|
||||
if c2nimConfig.recurse and c2nimConfig.inline:
|
||||
echo "Cannot use recurse and inline simultaneously"
|
||||
quit(1)
|
||||
|
||||
if not noprocess:
|
||||
var incls = c2nim(file, getNimout(sfile), c2nimConfig)
|
||||
if c2nimConfig.recurse and incls.len() != 0:
|
||||
var
|
||||
cfg = newOrderedTable[string, string]()
|
||||
|
||||
for name, value in c2nimConfig.fieldPairs:
|
||||
when value is string:
|
||||
cfg[name] = value
|
||||
when value is bool:
|
||||
cfg[name] = $value
|
||||
|
||||
for i in c2nimConfig.dynlib:
|
||||
cfg["dynlib." & i] = i
|
||||
|
||||
for inc in incls:
|
||||
runFile(inc, cfg)
|
||||
|
||||
proc runCfg*(cfg: string) =
|
||||
if not fileExists(cfg):
|
||||
echo "Config doesn't exist: " & cfg
|
||||
quit(1)
|
||||
|
||||
gProjectDir = parentDir(cfg.expandFilename())
|
||||
|
||||
gConfig = loadConfig(cfg)
|
||||
|
||||
if gConfig.hasKey("n.global"):
|
||||
if gConfig["n.global"].hasKey("output"):
|
||||
gOutput = gConfig["n.global"]["output"]
|
||||
if dirExists(gOutput):
|
||||
if "-f" in commandLineParams():
|
||||
try:
|
||||
removeDir(gOutput)
|
||||
except OSError:
|
||||
echo "Directory in use: " & gOutput
|
||||
quit(1)
|
||||
else:
|
||||
for f in walkFiles(gOutput/"*.nim"):
|
||||
try:
|
||||
removeFile(f)
|
||||
except OSError:
|
||||
echo "Unable to delete: " & f
|
||||
quit(1)
|
||||
createDir(gOutput)
|
||||
|
||||
if gConfig["n.global"].hasKey("cpp_compiler"):
|
||||
gCppCompiler = gConfig["n.global"]["cpp_compiler"]
|
||||
else:
|
||||
# Reset on a per project basis
|
||||
gCppCompiler = getEnv(cppCompilerEnv, defaultCppCompiler)
|
||||
|
||||
if gConfig["n.global"].hasKey("c_compiler"):
|
||||
gCCompiler = gConfig["n.global"]["c_compiler"]
|
||||
else:
|
||||
# Reset on a per project basis
|
||||
gCCompiler = getEnv(cCompilerEnv, defaultCCompiler)
|
||||
|
||||
if gConfig["n.global"].hasKey("filter"):
|
||||
gFilter = gConfig["n.global"]["filter"]
|
||||
if gConfig["n.global"].hasKey("quotes"):
|
||||
if gConfig["n.global"]["quotes"] == "false":
|
||||
gQuotes = false
|
||||
|
||||
if gConfig.hasKey("n.include"):
|
||||
for inc in gConfig["n.include"].keys():
|
||||
gIncludes.add(inc.addEnv())
|
||||
|
||||
if gConfig.hasKey("n.exclude"):
|
||||
for excl in gConfig["n.exclude"].keys():
|
||||
gExcludes.add(excl.addEnv())
|
||||
|
||||
if gConfig.hasKey("n.prepare"):
|
||||
for prep in gConfig["n.prepare"].keys():
|
||||
let (key, val) = getKey(prep)
|
||||
if val == true:
|
||||
let prepVal = gConfig["n.prepare"][prep]
|
||||
if key == "download":
|
||||
downloadUrl(prepVal)
|
||||
elif key == "extract":
|
||||
extractZip(prepVal)
|
||||
elif key == "git":
|
||||
gitRemotePull(prepVal)
|
||||
elif key == "gitremote":
|
||||
gitRemotePull(prepVal, false)
|
||||
elif key == "gitsparse":
|
||||
gitSparseCheckout(prepVal)
|
||||
elif key == "execute":
|
||||
discard execProc(prepVal)
|
||||
elif key == "copy":
|
||||
doCopy(prepVal)
|
||||
|
||||
if gConfig.hasKey("n.wildcard"):
|
||||
var wildcard = ""
|
||||
for wild in gConfig["n.wildcard"].keys():
|
||||
let (key, val) = getKey(wild)
|
||||
if val == true:
|
||||
if key == "wildcard":
|
||||
wildcard = gConfig["n.wildcard"][wild]
|
||||
else:
|
||||
gWildcards.setSectionKey(wildcard, wild,
|
||||
gConfig["n.wildcard"][wild])
|
||||
|
||||
for file in gConfig.keys():
|
||||
if file in @["n.global", "n.include", "n.exclude",
|
||||
"n.prepare", "n.wildcard", "n.post"]:
|
||||
continue
|
||||
|
||||
runFile(file, gConfig[file])
|
||||
|
||||
if gConfig.hasKey("n.post"):
|
||||
for post in gConfig["n.post"].keys():
|
||||
let (key, val) = getKey(post)
|
||||
if val == true:
|
||||
let postVal = gConfig["n.post"][post]
|
||||
if key == "reset":
|
||||
gitReset()
|
||||
elif key == "execute":
|
||||
discard execProc(postVal)
|
||||
100
src/nimgen/file.nim
Normal file
100
src/nimgen/file.nim
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import os, ospaths, pegs, regex, strutils, tables
|
||||
|
||||
import globals
|
||||
|
||||
# ###
|
||||
# File loction
|
||||
|
||||
proc getNimout*(file: string, rename=true): string =
|
||||
result = file.splitFile().name.replace(re"[\-\.]", "_") & ".nim"
|
||||
if gOutput != "":
|
||||
result = gOutput/result
|
||||
|
||||
if not rename:
|
||||
return
|
||||
|
||||
if gRenames.hasKey(file):
|
||||
result = gRenames[file]
|
||||
|
||||
if not dirExists(parentDir(result)):
|
||||
createDir(parentDir(result))
|
||||
|
||||
proc exclude*(file: string): bool =
|
||||
for excl in gExcludes:
|
||||
if excl in file:
|
||||
return true
|
||||
return false
|
||||
|
||||
proc search*(file: string): string =
|
||||
if exclude(file):
|
||||
return ""
|
||||
|
||||
result = file
|
||||
if file.splitFile().ext == ".nim":
|
||||
result = getNimout(file)
|
||||
elif not fileExists(result) and not dirExists(result):
|
||||
var found = false
|
||||
for inc in gIncludes:
|
||||
result = inc/file
|
||||
if fileExists(result) or dirExists(result):
|
||||
found = true
|
||||
break
|
||||
if not found:
|
||||
echo "File doesn't exist: " & file
|
||||
quit(1)
|
||||
|
||||
# Only keep relative directory
|
||||
return result.multiReplace([("\\", $DirSep), ("//", $DirSep), (gProjectDir & $DirSep, "")])
|
||||
|
||||
# ###
|
||||
# Loading / unloading
|
||||
|
||||
proc openRetry*(file: string, mode: FileMode = fmRead): File =
|
||||
while true:
|
||||
try:
|
||||
result = open(file, mode)
|
||||
break
|
||||
except IOError:
|
||||
sleep(100)
|
||||
|
||||
template withFile*(file: string, body: untyped): untyped =
|
||||
if fileExists(file):
|
||||
var f = openRetry(file)
|
||||
|
||||
var contentOrig = f.readAll()
|
||||
f.close()
|
||||
var content {.inject.} = contentOrig
|
||||
|
||||
body
|
||||
|
||||
if content != contentOrig:
|
||||
f = openRetry(file, fmWrite)
|
||||
write(f, content)
|
||||
f.close()
|
||||
else:
|
||||
echo "Missing file " & file
|
||||
|
||||
proc rename*(file: string, renfile: string) =
|
||||
if file.splitFile().ext == ".nim":
|
||||
return
|
||||
|
||||
var
|
||||
nimout = getNimout(file, false)
|
||||
newname = renfile.replace("$nimout", extractFilename(nimout))
|
||||
|
||||
if newname =~ peg"(!\$.)*{'$replace'\s*'('\s*{(!\)\S)+}')'}":
|
||||
var final = nimout.extractFilename()
|
||||
for entry in matches[1].split(","):
|
||||
let spl = entry.split("=")
|
||||
if spl.len() != 2:
|
||||
echo "Bad replace syntax: " & renfile
|
||||
quit(1)
|
||||
|
||||
let
|
||||
srch = spl[0].strip()
|
||||
repl = spl[1].strip()
|
||||
|
||||
final = final.replace(srch, repl)
|
||||
newname = newname.replace(matches[0], final)
|
||||
|
||||
gRenames[file] = gOutput/newname
|
||||
110
src/nimgen/fileops.nim
Normal file
110
src/nimgen/fileops.nim
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import os, regex, strutils
|
||||
|
||||
import file, prepare
|
||||
|
||||
# ###
|
||||
# Manipulating content
|
||||
|
||||
proc prepend*(file: string, data: string, search="") =
|
||||
withFile(file):
|
||||
if search == "":
|
||||
content = data & content
|
||||
else:
|
||||
let idx = content.find(search)
|
||||
if idx != -1:
|
||||
content = content[0..<idx] & data & content[idx..<content.len()]
|
||||
|
||||
proc pipe*(file: string, command: string) =
|
||||
let cmd = command % ["file", file]
|
||||
let commandResult = execProc(cmd).strip()
|
||||
if commandResult != "":
|
||||
withFile(file):
|
||||
content = commandResult
|
||||
|
||||
proc append*(file: string, data: string, search="") =
|
||||
withFile(file):
|
||||
if search == "":
|
||||
content &= data
|
||||
else:
|
||||
let idx = content.find(search)
|
||||
let idy = idx + search.len()
|
||||
if idx != -1:
|
||||
content = content[0..<idy] & data & content[idy..<content.len()]
|
||||
|
||||
proc freplace*(file: string, pattern: string, repl="") =
|
||||
withFile(file):
|
||||
if pattern in content:
|
||||
content = content.replace(pattern, repl)
|
||||
|
||||
proc freplace*(file: string, pattern: Regex, repl="") =
|
||||
withFile(file):
|
||||
var m: RegexMatch
|
||||
if content.find(pattern, m):
|
||||
if "$#" in repl:
|
||||
content = content.replace(pattern,
|
||||
proc (m: RegexMatch, s: string): string = repl % s[m.group(0)[0]])
|
||||
else:
|
||||
content = content.replace(pattern, repl)
|
||||
|
||||
proc comment*(file: string, pattern: string, numlines: string) =
|
||||
let
|
||||
ext = file.splitFile().ext.toLowerAscii()
|
||||
cmtchar = if ext == ".nim": "#" else: "//"
|
||||
|
||||
withFile(file):
|
||||
var
|
||||
idx = content.find(pattern)
|
||||
num = 0
|
||||
|
||||
try:
|
||||
num = numlines.parseInt()
|
||||
except ValueError:
|
||||
echo "Bad comment value, should be integer: " & numlines
|
||||
if idx != -1:
|
||||
for i in 0 .. num-1:
|
||||
if idx >= content.len():
|
||||
break
|
||||
content = content[0..<idx] & cmtchar & content[idx..<content.len()]
|
||||
while idx < content.len():
|
||||
idx += 1
|
||||
if content[idx] == '\L':
|
||||
idx += 1
|
||||
break
|
||||
|
||||
proc removeStatic*(filename: string) =
|
||||
## Replace static function bodies with a semicolon and commented
|
||||
## out body
|
||||
withFile(filename):
|
||||
content = content.replace(
|
||||
re"(?m)(static inline.*?\))(\s*\{(\s*?.*?$)*[\n\r]\})",
|
||||
proc (m: RegexMatch, s: string): string =
|
||||
let funcDecl = s[m.group(0)[0]]
|
||||
let body = s[m.group(1)[0]].strip()
|
||||
result = ""
|
||||
|
||||
result.add("$#;" % [funcDecl])
|
||||
result.add(body.replace(re"(?m)^", "//"))
|
||||
)
|
||||
|
||||
proc reAddStatic*(filename: string) =
|
||||
## Uncomment out the body and remove the semicolon. Undoes
|
||||
## removeStatic
|
||||
withFile(filename):
|
||||
content = content.replace(
|
||||
re"(?m)(static inline.*?\));(\/\/\s*\{(\s*?.*?$)*[\n\r]\/\/\})",
|
||||
proc (m: RegexMatch, s: string): string =
|
||||
let funcDecl = s[m.group(0)[0]]
|
||||
let body = s[m.group(1)[0]].strip()
|
||||
result = ""
|
||||
|
||||
result.add("$# " % [funcDecl])
|
||||
result.add(body.replace(re"(?m)^\/\/", ""))
|
||||
)
|
||||
|
||||
proc fixFuncProtos*(file: string) =
|
||||
withFile(file):
|
||||
content = content.replace(re"(?m)(^.*?)[ ]*\(\*(.*?)\((.*?)\)\)[ \r\n]*\((.*?[\r\n]*.*?)\);",
|
||||
proc (m: RegexMatch, s: string): string =
|
||||
(("typedef $# (*type_$#)($#);\n" % [s[m.group(0)[0]], s[m.group(1)[0]], s[m.group(3)[0]]]) &
|
||||
("type_$# $#($#);" % [s[m.group(1)[0]], s[m.group(1)[0]], s[m.group(2)[0]]]))
|
||||
)
|
||||
295
src/nimgen/gencore.nim
Normal file
295
src/nimgen/gencore.nim
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
import os, regex, ropes, sequtils, strutils, tables
|
||||
|
||||
import file, fileops, globals, prepare
|
||||
|
||||
proc addEnv*(str: string): string =
|
||||
var newStr = str
|
||||
for pair in envPairs():
|
||||
try:
|
||||
newStr = newStr % [pair.key, pair.value.string]
|
||||
except ValueError:
|
||||
# Ignore if there are no values to replace. We
|
||||
# want to continue anyway
|
||||
discard
|
||||
|
||||
try:
|
||||
newStr = newStr % ["output", gOutput]
|
||||
except ValueError:
|
||||
# Ignore if there are no values to replace. We
|
||||
# want to continue anyway
|
||||
discard
|
||||
|
||||
# if there are still format args, print a warning
|
||||
if newStr.contains("${"):
|
||||
echo "WARNING: \"", newStr, "\" still contains an uninterpolated value!"
|
||||
|
||||
return newStr
|
||||
|
||||
proc compile*(dir="", file=""): string =
|
||||
proc fcompile(file: string): string =
|
||||
return "{.compile: \"$#\".}" % file.replace("\\", "/")
|
||||
|
||||
var data = ""
|
||||
if dir != "" and dirExists(dir):
|
||||
for f in walkFiles(dir / "*.c"):
|
||||
data &= fcompile(f) & "\n"
|
||||
|
||||
if file != "" and fileExists(file):
|
||||
data &= fcompile(file) & "\n"
|
||||
|
||||
return data
|
||||
|
||||
proc getIncls*(file: string, inline=false): seq[string] =
|
||||
result = @[]
|
||||
if inline and file in gDoneInline:
|
||||
return
|
||||
|
||||
let curPath = splitFile(expandFileName(file)).dir
|
||||
withFile(file):
|
||||
for f in content.findAll(re"(?m)^\s*#\s*include\s+(.*?)$"):
|
||||
var inc = content[f.group(0)[0]].strip()
|
||||
if ((gQuotes and inc.contains("\"")) or (gFilter != "" and gFilter in inc)) and (not exclude(inc)):
|
||||
let addInc = inc.replace(re"""[<>"]""", "").replace(re"\/[\*\/].*$", "").strip()
|
||||
try:
|
||||
# Try searching for a local library. expandFilename will throw
|
||||
# OSError if the file does not exist
|
||||
let
|
||||
finc = expandFileName(curPath / addInc)
|
||||
fname = finc.replace(curPath & $DirSep, "")
|
||||
|
||||
if fname.len() > 0:
|
||||
# only add if the file is non-empty
|
||||
result.add(fname.search())
|
||||
except OSError:
|
||||
# If it's a system library
|
||||
result.add(addInc)
|
||||
|
||||
result = result.deduplicate()
|
||||
|
||||
gDoneInline.add(file)
|
||||
|
||||
if inline:
|
||||
var sres = newSeq[string]()
|
||||
for incl in result:
|
||||
let sincl = search(incl)
|
||||
if sincl == "":
|
||||
continue
|
||||
|
||||
sres.add(getIncls(sincl, inline))
|
||||
result.add(sres)
|
||||
|
||||
result = result.deduplicate()
|
||||
|
||||
proc getDefines*(file: string, inline=false): string =
|
||||
result = ""
|
||||
if inline:
|
||||
var incls = getIncls(file, inline)
|
||||
for incl in incls:
|
||||
let sincl = search(incl)
|
||||
if sincl != "":
|
||||
echo "Inlining " & sincl
|
||||
result &= getDefines(sincl)
|
||||
withFile(file):
|
||||
for def in content.findAll(re"(?m)^(\s*#\s*define\s+[\w\d_]+\s+[\d\-.xf]+)(?:\r|//|/*).*?$"):
|
||||
result &= content[def.group(0)[0]] & "\n"
|
||||
|
||||
proc runPreprocess*(file, ppflags, flags: string, inline: bool): string =
|
||||
var
|
||||
pproc = if flags.contains("cpp"): gCppCompiler else: gCCompiler
|
||||
cmd = "$# -E $# $#" % [pproc, ppflags, file]
|
||||
|
||||
for inc in gIncludes:
|
||||
cmd &= " -I " & inc
|
||||
|
||||
# Run preprocessor
|
||||
var data = execProc(cmd)
|
||||
|
||||
# Include content only from file
|
||||
var
|
||||
rdata: Rope
|
||||
start = false
|
||||
sfile = file.replace("\\", "/")
|
||||
|
||||
if inline:
|
||||
sfile = sfile.parentDir()
|
||||
for line in data.splitLines():
|
||||
if line.strip() != "":
|
||||
if line[0] == '#' and not line.contains("#pragma"):
|
||||
start = false
|
||||
if sfile in line.replace("\\", "/").replace("//", "/"):
|
||||
start = true
|
||||
if not ("\\" in line) and not ("/" in line) and extractFilename(sfile) in line:
|
||||
start = true
|
||||
else:
|
||||
if start:
|
||||
rdata.add(
|
||||
line.replace("_Noreturn", "")
|
||||
.replace("(())", "")
|
||||
.replace("WINAPI", "")
|
||||
.replace("__attribute__", "")
|
||||
.replace("extern \"C\"", "")
|
||||
.replace(re"\(\([_a-z]+?\)\)", "")
|
||||
.replace(re"\(\(__format__[\s]*\(__[gnu_]*printf__, [\d]+, [\d]+\)\)\);", ";") & "\n"
|
||||
)
|
||||
return $rdata
|
||||
|
||||
proc runCtags*(file: string): string =
|
||||
var
|
||||
cmd = "ctags -o - --fields=+S+K --c-kinds=+p --file-scope=no " & file
|
||||
fps = execProc(cmd)
|
||||
fdata = ""
|
||||
|
||||
for line in fps.splitLines():
|
||||
var spl = line.split(re"\t")
|
||||
if spl.len() > 4:
|
||||
if spl[0] != "main" and spl[3] != "member":
|
||||
var fn = ""
|
||||
var match: RegexMatch
|
||||
if spl[2].find(re"/\^(.*?)\(", match):
|
||||
fn = spl[2][match.group(0)[0]]
|
||||
fn &= spl[4].replace("signature:", "") & ";"
|
||||
fdata &= fn & "\n"
|
||||
|
||||
return fdata
|
||||
|
||||
template relativePath(path: untyped): untyped =
|
||||
path.multiReplace([(gOutput, ""), ("\\", "/"), ("//", "/")])
|
||||
|
||||
proc c2nim*(fl, outfile: string, c2nimConfig: c2nimConfigObj): seq[string] =
|
||||
var
|
||||
incls: seq[string] = @[]
|
||||
incout = ""
|
||||
file = search(fl)
|
||||
|
||||
if file == "":
|
||||
return
|
||||
|
||||
if file in gDoneRecursive:
|
||||
return
|
||||
|
||||
echo "Processing $# => $#" % [file, outfile]
|
||||
gDoneRecursive.add(file)
|
||||
|
||||
# Remove static inline function bodies
|
||||
removeStatic(file)
|
||||
|
||||
fixFuncProtos(file)
|
||||
|
||||
if c2nimConfig.recurse:
|
||||
incls = getIncls(file)
|
||||
for inc in incls:
|
||||
incout &= "import $#\n" % inc.search().getNimout()[0 .. ^5]
|
||||
|
||||
var cfile = file
|
||||
if c2nimConfig.preprocess:
|
||||
cfile = "temp-$#.c" % [outfile.extractFilename()]
|
||||
writeFile(cfile, runPreprocess(file, c2nimConfig.ppflags, c2nimConfig.flags, c2nimConfig.inline))
|
||||
elif c2nimConfig.ctags:
|
||||
cfile = "temp-$#.c" % [outfile.extractFilename()]
|
||||
writeFile(cfile, runCtags(file))
|
||||
|
||||
if c2nimConfig.defines and (c2nimConfig.preprocess or c2nimConfig.ctags):
|
||||
prepend(cfile, getDefines(file, c2nimConfig.inline))
|
||||
|
||||
var
|
||||
extflags = ""
|
||||
passC = ""
|
||||
outlib = ""
|
||||
outpragma = ""
|
||||
|
||||
passC = "import ospaths, strutils\n"
|
||||
|
||||
for inc in gIncludes:
|
||||
if inc.isAbsolute():
|
||||
passC &= ("""{.passC: "-I\"$#\"".}""" % [inc]) & "\n"
|
||||
else:
|
||||
passC &= (
|
||||
"""{.passC: "-I\"" & currentSourcePath().splitPath().head & "$#\"".}""" %
|
||||
inc.relativePath()
|
||||
) & "\n"
|
||||
|
||||
for prag in c2nimConfig.pragma:
|
||||
outpragma &= "{." & prag & ".}\n"
|
||||
|
||||
let fname = file.splitFile().name.replace(re"[\.\-]", "_")
|
||||
|
||||
if c2nimConfig.dynlib.len() != 0:
|
||||
let
|
||||
win = "when defined(Windows):\n"
|
||||
lin = "when defined(Linux):\n"
|
||||
osx = "when defined(MacOSX):\n"
|
||||
|
||||
var winlib, linlib, osxlib: string = ""
|
||||
for dl in c2nimConfig.dynlib:
|
||||
let
|
||||
lib = " const dynlib$# = \"$#\"\n" % [fname, dl]
|
||||
ext = dl.splitFile().ext
|
||||
|
||||
if ext == ".dll":
|
||||
winlib &= lib
|
||||
elif ext == ".so":
|
||||
linlib &= lib
|
||||
elif ext == ".dylib":
|
||||
osxlib &= lib
|
||||
|
||||
if winlib != "":
|
||||
outlib &= win & winlib & "\n"
|
||||
if linlib != "":
|
||||
outlib &= lin & linlib & "\n"
|
||||
if osxlib != "":
|
||||
outlib &= osx & osxlib & "\n"
|
||||
|
||||
if outlib != "":
|
||||
extflags &= " --dynlib:dynlib$#" % fname
|
||||
else:
|
||||
if file.isAbsolute():
|
||||
passC &= "const header$# = \"$#\"\n" % [fname, file]
|
||||
else:
|
||||
passC &= "const header$# = currentSourcePath().splitPath().head & \"$#\"\n" %
|
||||
[fname, file.relativePath()]
|
||||
extflags = "--header:header$#" % fname
|
||||
|
||||
# Run c2nim on generated file
|
||||
var cmd = "c2nim $# $# --out:$# $#" % [c2nimConfig.flags, extflags, outfile, cfile]
|
||||
when defined(windows):
|
||||
cmd = "cmd /c " & cmd
|
||||
discard execProc(cmd)
|
||||
|
||||
if c2nimConfig.preprocess or c2nimConfig.ctags:
|
||||
try:
|
||||
removeFile(cfile)
|
||||
except:
|
||||
discard
|
||||
|
||||
# Import nim modules
|
||||
if c2nimConfig.recurse:
|
||||
prepend(outfile, incout)
|
||||
|
||||
# Nim doesn't like {.cdecl.} for type proc()
|
||||
freplace(outfile, re"(?m)(.*? = proc.*?)\{.cdecl.\}", "$#")
|
||||
freplace(outfile, " {.cdecl.})", ")")
|
||||
|
||||
# Include {.compile.} directives
|
||||
for cpl in c2nimConfig.compile:
|
||||
let fcpl = search(cpl)
|
||||
if getFileInfo(fcpl).kind == pcFile:
|
||||
prepend(outfile, compile(file=fcpl))
|
||||
else:
|
||||
prepend(outfile, compile(dir=fcpl))
|
||||
|
||||
# Add any pragmas
|
||||
if outpragma != "":
|
||||
prepend(outfile, outpragma)
|
||||
|
||||
# Add header file and include paths
|
||||
if passC != "":
|
||||
prepend(outfile, passC)
|
||||
|
||||
# Add dynamic library
|
||||
if outlib != "":
|
||||
prepend(outfile, outlib)
|
||||
|
||||
# Add back static functions for compilation
|
||||
reAddStatic(file)
|
||||
|
||||
return incls
|
||||
40
src/nimgen/globals.nim
Normal file
40
src/nimgen/globals.nim
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import os, parsecfg, tables
|
||||
|
||||
const
|
||||
cCompilerEnv* = "CC"
|
||||
cppCompilerEnv* = "CPP"
|
||||
defaultCCompiler* = "gcc"
|
||||
defaultCppCompiler* = "g++"
|
||||
|
||||
var
|
||||
gDoneRecursive*: seq[string] = @[]
|
||||
gDoneInline*: seq[string] = @[]
|
||||
|
||||
gProjectDir* = ""
|
||||
gConfig*: Config
|
||||
gFilter* = ""
|
||||
gQuotes* = true
|
||||
gCppCompiler* = getEnv(cppCompilerEnv, defaultCCompiler)
|
||||
gCCompiler* = getEnv(cCompilerEnv, defaultCppCompiler)
|
||||
gOutput* = ""
|
||||
gIncludes*: seq[string] = @[]
|
||||
gExcludes*: seq[string] = @[]
|
||||
gRenames* = initTable[string, string]()
|
||||
gWildcards* = newConfig()
|
||||
|
||||
type
|
||||
c2nimConfigObj* = object
|
||||
flags*, ppflags*: string
|
||||
recurse*, inline*, preprocess*, ctags*, defines*: bool
|
||||
dynlib*, compile*, pragma*: seq[string]
|
||||
|
||||
const gDoc* = """
|
||||
Nimgen is a helper for c2nim to simpilfy and automate the wrapping of C libraries
|
||||
|
||||
Usage:
|
||||
nimgen [options] <file.cfg>...
|
||||
|
||||
Options:
|
||||
-f delete all artifacts and regenerate
|
||||
"""
|
||||
|
||||
117
src/nimgen/prepare.nim
Normal file
117
src/nimgen/prepare.nim
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import os, osproc, streams, strutils
|
||||
|
||||
import globals
|
||||
|
||||
proc execProc*(cmd: string): string =
|
||||
result = ""
|
||||
var
|
||||
p = startProcess(cmd, options = {poStdErrToStdOut, poUsePath, poEvalCommand})
|
||||
|
||||
outp = outputStream(p)
|
||||
line = newStringOfCap(120).TaintedString
|
||||
|
||||
while true:
|
||||
if outp.readLine(line):
|
||||
result.add(line)
|
||||
result.add("\n")
|
||||
elif not running(p): break
|
||||
|
||||
var x = p.peekExitCode()
|
||||
if x != 0:
|
||||
echo "Command failed: " & $x
|
||||
echo cmd
|
||||
echo result
|
||||
quit(1)
|
||||
|
||||
proc extractZip*(zipfile: string) =
|
||||
var cmd = "unzip -o $#"
|
||||
if defined(Windows):
|
||||
cmd = "powershell -nologo -noprofile -command \"& { Add-Type -A " &
|
||||
"'System.IO.Compression.FileSystem'; " &
|
||||
"[IO.Compression.ZipFile]::ExtractToDirectory('$#', '.'); }\""
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
echo "Extracting " & zipfile
|
||||
discard execProc(cmd % zipfile)
|
||||
|
||||
proc downloadUrl*(url: string) =
|
||||
let
|
||||
file = url.extractFilename()
|
||||
ext = file.splitFile().ext.toLowerAscii()
|
||||
|
||||
var cmd = "curl $# -o $#"
|
||||
if defined(Windows):
|
||||
cmd = "powershell wget $# -OutFile $#"
|
||||
|
||||
if not (ext == ".zip" and fileExists(gOutput/file)):
|
||||
echo "Downloading " & file
|
||||
discard execProc(cmd % [url, gOutput/file])
|
||||
|
||||
if ext == ".zip":
|
||||
extractZip(file)
|
||||
|
||||
proc gitReset*() =
|
||||
echo "Resetting Git repo"
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
discard execProc("git reset --hard HEAD")
|
||||
|
||||
proc gitCheckout*(filename: string) {.used.} =
|
||||
echo "Resetting file: $#" % [filename]
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
let adjustedFile = filename.replace(gOutput & $DirSep, "")
|
||||
|
||||
discard execProc("git checkout $#" % [adjustedFile])
|
||||
|
||||
proc gitRemotePull*(url: string, pull=true) =
|
||||
if dirExists(gOutput/".git"):
|
||||
if pull:
|
||||
gitReset()
|
||||
return
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
echo "Setting up Git repo"
|
||||
discard execProc("git init .")
|
||||
discard execProc("git remote add origin " & url)
|
||||
|
||||
if pull:
|
||||
echo "Checking out artifacts"
|
||||
discard execProc("git pull --depth=1 origin master")
|
||||
|
||||
proc gitSparseCheckout*(plist: string) =
|
||||
let sparsefile = ".git/info/sparse-checkout"
|
||||
if fileExists(gOutput/sparsefile):
|
||||
gitReset()
|
||||
return
|
||||
|
||||
setCurrentDir(gOutput)
|
||||
defer: setCurrentDir(gProjectDir)
|
||||
|
||||
discard execProc("git config core.sparsecheckout true")
|
||||
writeFile(sparsefile, plist)
|
||||
|
||||
echo "Checking out artifacts"
|
||||
discard execProc("git pull --depth=1 origin master")
|
||||
|
||||
proc doCopy*(flist: string) =
|
||||
for pair in flist.split(","):
|
||||
let spl = pair.split("=")
|
||||
if spl.len() != 2:
|
||||
echo "Bad copy syntax: " & flist
|
||||
quit(1)
|
||||
|
||||
let
|
||||
lfile = spl[0].strip()
|
||||
rfile = spl[1].strip()
|
||||
|
||||
copyFile(lfile, rfile)
|
||||
echo "Copied $# to $#" % [lfile, rfile]
|
||||
Loading…
Add table
Add a link
Reference in a new issue