1+ # # Utility API for Nim package managers.
2+ # # (c) 2021 Andreas Rumpf
3+
4+ import std/ strutils
5+
6+ import compiler/ [ast, idents, msgs, syntaxes, options, pathutils, lineinfos]
7+ import version, packageinfotypes, packageinfo, options, packageparser
8+
9+ type NimbleFileInfo * = object
10+ requires* : seq [string ]
11+ srcDir* : string
12+ version* : string
13+ tasks* : seq [(string , string )]
14+ hasInstallHooks* : bool
15+ hasErrors* : bool
16+
17+ proc eqIdent (a, b: string ): bool {.inline .} =
18+ cmpIgnoreCase (a, b) == 0 and a[0 ] == b[0 ]
19+
20+ proc extract (n: PNode , conf: ConfigRef , result: var NimbleFileInfo ) =
21+ case n.kind
22+ of nkStmtList, nkStmtListExpr:
23+ for child in n:
24+ extract (child, conf, result )
25+ of nkCallKinds:
26+ if n[0 ].kind == nkIdent:
27+ case n[0 ].ident.s
28+ of " requires" :
29+ for i in 1 ..< n.len:
30+ var ch = n[i]
31+ while ch.kind in {nkStmtListExpr, nkStmtList} and ch.len > 0 :
32+ ch = ch.lastSon
33+ if ch.kind in {nkStrLit .. nkTripleStrLit}:
34+ result .requires.add ch.strVal
35+ else :
36+ localError (conf, ch.info, " 'requires' takes string literals" )
37+ result .hasErrors = true
38+ of " task" :
39+ if n.len >= 3 and n[1 ].kind == nkIdent and
40+ n[2 ].kind in {nkStrLit .. nkTripleStrLit}:
41+ result .tasks.add ((n[1 ].ident.s, n[2 ].strVal))
42+ of " before" , " after" :
43+ #[
44+ before install do:
45+ exec "git submodule update --init"
46+ var make = "make"
47+ when defined(windows):
48+ make = "mingw32-make"
49+ exec make
50+ ]#
51+ if n.len >= 3 and n[1 ].kind == nkIdent and n[1 ].ident.s == " install" :
52+ result .hasInstallHooks = true
53+ else :
54+ discard
55+ of nkAsgn, nkFastAsgn:
56+ if n[0 ].kind == nkIdent and eqIdent (n[0 ].ident.s, " srcDir" ):
57+ if n[1 ].kind in {nkStrLit .. nkTripleStrLit}:
58+ result .srcDir = n[1 ].strVal
59+ else :
60+ localError (conf, n[1 ].info, " assignments to 'srcDir' must be string literals" )
61+ result .hasErrors = true
62+ elif n[0 ].kind == nkIdent and eqIdent (n[0 ].ident.s, " version" ):
63+ if n[1 ].kind in {nkStrLit .. nkTripleStrLit}:
64+ result .version = n[1 ].strVal
65+ else :
66+ localError (conf, n[1 ].info, " assignments to 'version' must be string literals" )
67+ result .hasErrors = true
68+ else :
69+ discard
70+
71+ proc extractRequiresInfo * (nimbleFile: string ): NimbleFileInfo =
72+ # # Extract the `requires` information from a Nimble file. This does **not**
73+ # # evaluate the Nimble file. Errors are produced on stderr/stdout and are
74+ # # formatted as the Nim compiler does it. The parser uses the Nim compiler
75+ # # as an API. The result can be empty, this is not an error, only parsing
76+ # # errors are reported.
77+ var conf = newConfigRef ()
78+ conf.foreignPackageNotes = {}
79+ conf.notes = {}
80+ conf.mainPackageNotes = {}
81+ conf.errorMax = high (int )
82+ conf.structuredErrorHook = proc (
83+ config: ConfigRef , info: TLineInfo , msg: string , severity: Severity
84+ ) {.gcsafe .} =
85+ localError (config, info, warnUser, msg)
86+
87+ let fileIdx = fileInfoIdx (conf, AbsoluteFile nimbleFile)
88+ var parser: Parser
89+ if setupParser (parser, fileIdx, newIdentCache (), conf):
90+ extract (parseAll (parser), conf, result )
91+ closeParser (parser)
92+ result .hasErrors = result .hasErrors or conf.errorCounter > 0
93+
94+ type PluginInfo * = object
95+ builderPatterns* : seq [(string , string )]
96+
97+ proc extractPlugin (
98+ nimscriptFile: string , n: PNode , conf: ConfigRef , result: var PluginInfo
99+ ) =
100+ case n.kind
101+ of nkStmtList, nkStmtListExpr:
102+ for child in n:
103+ extractPlugin (nimscriptFile, child, conf, result )
104+ of nkCallKinds:
105+ if n[0 ].kind == nkIdent:
106+ case n[0 ].ident.s
107+ of " builder" :
108+ if n.len >= 3 and n[1 ].kind in {nkStrLit .. nkTripleStrLit}:
109+ result .builderPatterns.add ((n[1 ].strVal, nimscriptFile))
110+ else :
111+ discard
112+ else :
113+ discard
114+
115+ proc extractPluginInfo * (nimscriptFile: string , info: var PluginInfo ) =
116+ var conf = newConfigRef ()
117+ conf.foreignPackageNotes = {}
118+ conf.notes = {}
119+ conf.mainPackageNotes = {}
120+
121+ let fileIdx = fileInfoIdx (conf, AbsoluteFile nimscriptFile)
122+ var parser: Parser
123+ if setupParser (parser, fileIdx, newIdentCache (), conf):
124+ extractPlugin (nimscriptFile, parseAll (parser), conf, info)
125+ closeParser (parser)
126+
127+ const Operators * = {'<' , '>' , '=' , '&' , '@' , '!' , '^' }
128+
129+ proc token (s: string , idx: int , lit: var string ): int =
130+ var i = idx
131+ if i >= s.len:
132+ return i
133+ while s[i] in Whitespace :
134+ inc (i)
135+ case s[i]
136+ of Letters , '#' :
137+ lit.add s[i]
138+ inc i
139+ while i < s.len and s[i] notin (Whitespace + {'@' , '#' }):
140+ lit.add s[i]
141+ inc i
142+ of '0' .. '9' :
143+ while i < s.len and s[i] in {'0' .. '9' , '.' }:
144+ lit.add s[i]
145+ inc i
146+ of '"' :
147+ inc i
148+ while i < s.len and s[i] != '"' :
149+ lit.add s[i]
150+ inc i
151+ inc i
152+ of Operators :
153+ while i < s.len and s[i] in Operators :
154+ lit.add s[i]
155+ inc i
156+ else :
157+ lit.add s[i]
158+ inc i
159+ result = i
160+
161+ iterator tokenizeRequires * (s: string ): string =
162+ var start = 0
163+ var tok = " "
164+ while start < s.len:
165+ tok.setLen 0
166+ start = token (s, start, tok)
167+ yield tok
168+
169+ proc getRequires * (nimbleFileInfo: NimbleFileInfo ): seq [PkgTuple ] =
170+ for require in nimbleFileInfo.requires:
171+ result .add (parseRequires (require))
172+
173+ proc toRequiresInfo * (pkgInfo: PackageInfo , options: Options ): PackageInfo =
174+ # For nim we only need the version. Since version is usually in the form of `version = $NimMajor & "." & $NimMinor & "." & $NimPatch
175+ # we need to use the vm to get the version. Another option could be to use the binary and ask for the version
176+ if pkgInfo.basicInfo.name.isNim:
177+ return pkgInfo.toFullInfo (options)
178+
179+ let nimbleFileInfo = extractRequiresInfo (pkgInfo.myPath)
180+ result = pkgInfo
181+ result .requires = getRequires (nimbleFileInfo)
182+ result .infoKind = pikRequires
183+
184+ when isMainModule :
185+ for x in tokenizeRequires (" jester@#head >= 1.5 & <= 1.8" ):
186+ echo x
0 commit comments