os.nim 108 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module contains basic operating system facilities like
  10. ## retrieving environment variables, reading command line arguments,
  11. ## working with directories, running shell commands, etc.
  12. ##
  13. ## .. code-block::
  14. ## import os
  15. ##
  16. ## let myFile = "/path/to/my/file.nim"
  17. ##
  18. ## let pathSplit = splitPath(myFile)
  19. ## assert pathSplit.head == "/path/to/my"
  20. ## assert pathSplit.tail == "file.nim"
  21. ##
  22. ## assert parentDir(myFile) == "/path/to/my"
  23. ##
  24. ## let fileSplit = splitFile(myFile)
  25. ## assert fileSplit.dir == "/path/to/my"
  26. ## assert fileSplit.name == "file"
  27. ## assert fileSplit.ext == ".nim"
  28. ##
  29. ## assert myFile.changeFileExt("c") == "/path/to/my/file.c"
  30. ##
  31. ##
  32. ## **See also:**
  33. ## * `osproc module <osproc.html>`_ for process communication beyond
  34. ## `execShellCmd proc <#execShellCmd,string>`_
  35. ## * `parseopt module <parseopt.html>`_ for command-line parser beyond
  36. ## `parseCmdLine proc <#parseCmdLine,string>`_
  37. ## * `uri module <uri.html>`_
  38. ## * `distros module <distros.html>`_
  39. ## * `dynlib module <dynlib.html>`_
  40. ## * `streams module <streams.html>`_
  41. {.deadCodeElim: on.} # dce option deprecated
  42. {.push debugger: off.}
  43. include "system/inclrtl"
  44. import
  45. strutils, pathnorm
  46. const weirdTarget = defined(nimscript) or defined(js)
  47. when weirdTarget:
  48. discard
  49. elif defined(windows):
  50. import winlean, times
  51. elif defined(posix):
  52. import posix, times
  53. proc toTime(ts: Timespec): times.Time {.inline.} =
  54. result = initTime(ts.tv_sec.int64, ts.tv_nsec.int)
  55. else:
  56. {.error: "OS module not ported to your operating system!".}
  57. when weirdTarget and defined(nimErrorProcCanHaveBody):
  58. {.pragma: noNimScript, error: "this proc is not available on the NimScript target".}
  59. else:
  60. {.pragma: noNimScript.}
  61. type
  62. ReadEnvEffect* = object of ReadIOEffect ## Effect that denotes a read
  63. ## from an environment variable.
  64. WriteEnvEffect* = object of WriteIOEffect ## Effect that denotes a write
  65. ## to an environment variable.
  66. ReadDirEffect* = object of ReadIOEffect ## Effect that denotes a read
  67. ## operation from the directory
  68. ## structure.
  69. WriteDirEffect* = object of WriteIOEffect ## Effect that denotes a write
  70. ## operation to
  71. ## the directory structure.
  72. OSErrorCode* = distinct int32 ## Specifies an OS Error Code.
  73. include "includes/osseps"
  74. proc normalizePathEnd(path: var string, trailingSep = false) =
  75. ## Ensures ``path`` has exactly 0 or 1 trailing `DirSep`, depending on
  76. ## ``trailingSep``, and taking care of edge cases: it preservers whether
  77. ## a path is absolute or relative, and makes sure trailing sep is `DirSep`,
  78. ## not `AltSep`.
  79. if path.len == 0: return
  80. var i = path.len
  81. while i >= 1 and path[i-1] in {DirSep, AltSep}: dec(i)
  82. if trailingSep:
  83. # foo// => foo
  84. path.setLen(i)
  85. # foo => foo/
  86. path.add DirSep
  87. elif i > 0:
  88. # foo// => foo
  89. path.setLen(i)
  90. else:
  91. # // => / (empty case was already taken care of)
  92. path = $DirSep
  93. proc normalizePathEnd(path: string, trailingSep = false): string =
  94. result = path
  95. result.normalizePathEnd(trailingSep)
  96. proc joinPath*(head, tail: string): string {.
  97. noSideEffect, rtl, extern: "nos$1".} =
  98. ## Joins two directory names to one.
  99. ##
  100. ## If `head` is the empty string, `tail` is returned. If `tail` is the empty
  101. ## string, `head` is returned with a trailing path separator. If `tail` starts
  102. ## with a path separator it will be removed when concatenated to `head`.
  103. ## Path separators will be normalized.
  104. ##
  105. ## See also:
  106. ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_
  107. ## * `/ proc <#/,string,string>`_
  108. ## * `splitPath proc <#splitPath,string>`_
  109. ## * `uri.combine proc <uri.html#combine,Uri,Uri>`_
  110. ## * `uri./ proc <uri.html#/,Uri,string>`_
  111. runnableExamples:
  112. when defined(posix):
  113. assert joinPath("usr", "lib") == "usr/lib"
  114. assert joinPath("usr", "") == "usr/"
  115. assert joinPath("", "") == ""
  116. assert joinPath("", "lib") == "lib"
  117. assert joinPath("", "/lib") == "/lib"
  118. assert joinPath("usr/", "/lib") == "usr/lib"
  119. assert joinPath("usr/lib", "../bin") == "usr/bin"
  120. result = newStringOfCap(head.len + tail.len)
  121. var state = 0
  122. addNormalizePath(head, result, state, DirSep)
  123. if result.len != 0 and result[^1] notin {DirSep, AltSep} and tail.len == 0:
  124. result.add DirSep
  125. else:
  126. addNormalizePath(tail, result, state, DirSep)
  127. when false:
  128. if len(head) == 0:
  129. result = tail
  130. elif head[len(head)-1] in {DirSep, AltSep}:
  131. if tail.len > 0 and tail[0] in {DirSep, AltSep}:
  132. result = head & substr(tail, 1)
  133. else:
  134. result = head & tail
  135. else:
  136. if tail.len > 0 and tail[0] in {DirSep, AltSep}:
  137. result = head & tail
  138. else:
  139. result = head & DirSep & tail
  140. proc joinPath*(parts: varargs[string]): string {.noSideEffect,
  141. rtl, extern: "nos$1OpenArray".} =
  142. ## The same as `joinPath(head, tail) proc <#joinPath,string,string>`_,
  143. ## but works with any number of directory parts.
  144. ##
  145. ## You need to pass at least one element or the proc
  146. ## will assert in debug builds and crash on release builds.
  147. ##
  148. ## See also:
  149. ## * `joinPath(head, tail) proc <#joinPath,string,string>`_
  150. ## * `/ proc <#/,string,string>`_
  151. ## * `/../ proc <#/../,string,string>`_
  152. ## * `splitPath proc <#splitPath,string>`_
  153. runnableExamples:
  154. when defined(posix):
  155. assert joinPath("a") == "a"
  156. assert joinPath("a", "b", "c") == "a/b/c"
  157. assert joinPath("usr/lib", "../../var", "log") == "var/log"
  158. var estimatedLen = 0
  159. for p in parts: estimatedLen += p.len
  160. result = newStringOfCap(estimatedLen)
  161. var state = 0
  162. for i in 0..high(parts):
  163. addNormalizePath(parts[i], result, state, DirSep)
  164. proc `/`*(head, tail: string): string {.noSideEffect.} =
  165. ## The same as `joinPath(head, tail) proc <#joinPath,string,string>`_.
  166. ##
  167. ## See also:
  168. ## * `/../ proc <#/../,string,string>`_
  169. ## * `joinPath(head, tail) proc <#joinPath,string,string>`_
  170. ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_
  171. ## * `splitPath proc <#splitPath,string>`_
  172. ## * `uri.combine proc <uri.html#combine,Uri,Uri>`_
  173. ## * `uri./ proc <uri.html#/,Uri,string>`_
  174. runnableExamples:
  175. when defined(posix):
  176. assert "usr" / "" == "usr/"
  177. assert "" / "lib" == "lib"
  178. assert "" / "/lib" == "/lib"
  179. assert "usr/" / "/lib" == "usr/lib"
  180. assert "usr" / "lib" / "../bin" == "usr/bin"
  181. return joinPath(head, tail)
  182. proc splitPath*(path: string): tuple[head, tail: string] {.
  183. noSideEffect, rtl, extern: "nos$1".} =
  184. ## Splits a directory into `(head, tail)` tuple, so that
  185. ## ``head / tail == path`` (except for edge cases like "/usr").
  186. ##
  187. ## See also:
  188. ## * `joinPath(head, tail) proc <#joinPath,string,string>`_
  189. ## * `joinPath(varargs) proc <#joinPath,varargs[string]>`_
  190. ## * `/ proc <#/,string,string>`_
  191. ## * `/../ proc <#/../,string,string>`_
  192. ## * `relativePath proc <#relativePath,string,string>`_
  193. runnableExamples:
  194. assert splitPath("usr/local/bin") == ("usr/local", "bin")
  195. assert splitPath("usr/local/bin/") == ("usr/local/bin", "")
  196. assert splitPath("bin") == ("", "bin")
  197. assert splitPath("/bin") == ("", "bin")
  198. assert splitPath("") == ("", "")
  199. var sepPos = -1
  200. for i in countdown(len(path)-1, 0):
  201. if path[i] in {DirSep, AltSep}:
  202. sepPos = i
  203. break
  204. if sepPos >= 0:
  205. result.head = substr(path, 0, sepPos-1)
  206. result.tail = substr(path, sepPos+1)
  207. else:
  208. result.head = ""
  209. result.tail = path
  210. when FileSystemCaseSensitive:
  211. template `!=?`(a, b: char): bool = a != b
  212. else:
  213. template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b)
  214. proc relativePath*(path, base: string; sep = DirSep): string {.
  215. noSideEffect, rtl, extern: "nos$1", raises: [].} =
  216. ## Converts `path` to a path relative to `base`.
  217. ##
  218. ## The `sep` (default: `DirSep <#DirSep>`_) is used for the path normalizations,
  219. ## this can be useful to ensure the relative path only contains `'/'`
  220. ## so that it can be used for URL constructions.
  221. ##
  222. ## See also:
  223. ## * `splitPath proc <#splitPath,string>`_
  224. ## * `parentDir proc <#parentDir,string>`_
  225. ## * `tailDir proc <#tailDir,string>`_
  226. runnableExamples:
  227. assert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim"
  228. assert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim"
  229. assert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"
  230. assert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim"
  231. assert relativePath("", "/users/moo", '/') == ""
  232. # Todo: If on Windows, path and base do not agree on the drive letter,
  233. # return `path` as is.
  234. if path.len == 0: return ""
  235. var f, b: PathIter
  236. var ff = (0, -1)
  237. var bb = (0, -1) # (int, int)
  238. result = newStringOfCap(path.len)
  239. # skip the common prefix:
  240. while f.hasNext(path) and b.hasNext(base):
  241. ff = next(f, path)
  242. bb = next(b, base)
  243. let diff = ff[1] - ff[0]
  244. if diff != bb[1] - bb[0]: break
  245. var same = true
  246. for i in 0..diff:
  247. if path[i + ff[0]] !=? base[i + bb[0]]:
  248. same = false
  249. break
  250. if not same: break
  251. ff = (0, -1)
  252. bb = (0, -1)
  253. # for i in 0..diff:
  254. # result.add base[i + bb[0]]
  255. # /foo/bar/xxx/ -- base
  256. # /foo/bar/baz -- path path
  257. # ../baz
  258. # every directory that is in 'base', needs to add '..'
  259. while true:
  260. if bb[1] >= bb[0]:
  261. if result.len > 0 and result[^1] != sep:
  262. result.add sep
  263. result.add ".."
  264. if not b.hasNext(base): break
  265. bb = b.next(base)
  266. # add the rest of 'path':
  267. while true:
  268. if ff[1] >= ff[0]:
  269. if result.len > 0 and result[^1] != sep:
  270. result.add sep
  271. for i in 0..ff[1] - ff[0]:
  272. result.add path[i + ff[0]]
  273. if not f.hasNext(path): break
  274. ff = f.next(path)
  275. proc parentDirPos(path: string): int =
  276. var q = 1
  277. if len(path) >= 1 and path[len(path)-1] in {DirSep, AltSep}: q = 2
  278. for i in countdown(len(path)-q, 0):
  279. if path[i] in {DirSep, AltSep}: return i
  280. result = -1
  281. proc parentDir*(path: string): string {.
  282. noSideEffect, rtl, extern: "nos$1".} =
  283. ## Returns the parent directory of `path`.
  284. ##
  285. ## This is the same as ``splitPath(path).head`` when ``path`` doesn't end
  286. ## in a dir separator.
  287. ## The remainder can be obtained with `lastPathPart(path) proc
  288. ## <#lastPathPart,string>`_.
  289. ##
  290. ## See also:
  291. ## * `relativePath proc <#relativePath,string,string>`_
  292. ## * `splitPath proc <#splitPath,string>`_
  293. ## * `tailDir proc <#tailDir,string>`_
  294. ## * `parentDirs iterator <#parentDirs.i,string>`_
  295. runnableExamples:
  296. assert parentDir("") == ""
  297. when defined(posix):
  298. assert parentDir("/usr/local/bin") == "/usr/local"
  299. assert parentDir("foo/bar/") == "foo"
  300. assert parentDir("foo/bar//") == "foo"
  301. assert parentDir("//foo//bar//") == "//foo"
  302. assert parentDir("./foo") == "."
  303. assert parentDir("/foo") == ""
  304. result = normalizePathEnd(path)
  305. var sepPos = parentDirPos(result)
  306. if sepPos >= 0:
  307. while sepPos >= 0 and result[sepPos] in {DirSep, AltSep}: dec sepPos
  308. result = substr(result, 0, sepPos)
  309. else:
  310. result = ""
  311. proc tailDir*(path: string): string {.
  312. noSideEffect, rtl, extern: "nos$1".} =
  313. ## Returns the tail part of `path`.
  314. ##
  315. ## See also:
  316. ## * `relativePath proc <#relativePath,string,string>`_
  317. ## * `splitPath proc <#splitPath,string>`_
  318. ## * `parentDir proc <#parentDir,string>`_
  319. runnableExamples:
  320. assert tailDir("/bin") == "bin"
  321. assert tailDir("bin") == ""
  322. assert tailDir("bin/") == ""
  323. assert tailDir("/usr/local/bin") == "usr/local/bin"
  324. assert tailDir("//usr//local//bin//") == "usr//local//bin//"
  325. assert tailDir("./usr/local/bin") == "usr/local/bin"
  326. assert tailDir("usr/local/bin") == "local/bin"
  327. var i = 0
  328. while i < len(path):
  329. if path[i] in {DirSep, AltSep}:
  330. while i < len(path) and path[i] in {DirSep, AltSep}: inc i
  331. return substr(path, i)
  332. inc i
  333. result = ""
  334. proc isRootDir*(path: string): bool {.
  335. noSideEffect, rtl, extern: "nos$1".} =
  336. ## Checks whether a given `path` is a root directory.
  337. runnableExamples:
  338. assert isRootDir("")
  339. assert isRootDir(".")
  340. assert isRootDir("/")
  341. assert isRootDir("a")
  342. assert not isRootDir("/a")
  343. assert not isRootDir("a/b/c")
  344. result = parentDirPos(path) < 0
  345. iterator parentDirs*(path: string, fromRoot=false, inclusive=true): string =
  346. ## Walks over all parent directories of a given `path`.
  347. ##
  348. ## If `fromRoot` is true (default: false), the traversal will start from
  349. ## the file system root directory.
  350. ## If `inclusive` is true (default), the original argument will be included
  351. ## in the traversal.
  352. ##
  353. ## Relative paths won't be expanded by this iterator. Instead, it will traverse
  354. ## only the directories appearing in the relative path.
  355. ##
  356. ## See also:
  357. ## * `parentDir proc <#parentDir,string>`_
  358. ##
  359. ## **Examples:**
  360. ##
  361. ## .. code-block::
  362. ## let g = "a/b/c"
  363. ##
  364. ## for p in g.parentDirs:
  365. ## echo p
  366. ## # a/b/c
  367. ## # a/b
  368. ## # a
  369. ##
  370. ## for p in g.parentDirs(fromRoot=true):
  371. ## echo p
  372. ## # a/
  373. ## # a/b/
  374. ## # a/b/c
  375. ##
  376. ## for p in g.parentDirs(inclusive=false):
  377. ## echo p
  378. ## # a/b
  379. ## # a
  380. if not fromRoot:
  381. var current = path
  382. if inclusive: yield path
  383. while true:
  384. if current.isRootDir: break
  385. current = current.parentDir
  386. yield current
  387. else:
  388. for i in countup(0, path.len - 2): # ignore the last /
  389. # deal with non-normalized paths such as /foo//bar//baz
  390. if path[i] in {DirSep, AltSep} and
  391. (i == 0 or path[i-1] notin {DirSep, AltSep}):
  392. yield path.substr(0, i)
  393. if inclusive: yield path
  394. proc `/../`*(head, tail: string): string {.noSideEffect.} =
  395. ## The same as ``parentDir(head) / tail``, unless there is no parent
  396. ## directory. Then ``head / tail`` is performed instead.
  397. ##
  398. ## See also:
  399. ## * `/ proc <#/,string,string>`_
  400. ## * `parentDir proc <#parentDir,string>`_
  401. runnableExamples:
  402. when defined(posix):
  403. assert "a/b/c" /../ "d/e" == "a/b/d/e"
  404. assert "a" /../ "d/e" == "a/d/e"
  405. let sepPos = parentDirPos(head)
  406. if sepPos >= 0:
  407. result = substr(head, 0, sepPos-1) / tail
  408. else:
  409. result = head / tail
  410. proc normExt(ext: string): string =
  411. if ext == "" or ext[0] == ExtSep: result = ext # no copy needed here
  412. else: result = ExtSep & ext
  413. proc searchExtPos*(path: string): int =
  414. ## Returns index of the `'.'` char in `path` if it signifies the beginning
  415. ## of extension. Returns -1 otherwise.
  416. ##
  417. ## See also:
  418. ## * `splitFile proc <#splitFile,string>`_
  419. ## * `extractFilename proc <#extractFilename,string>`_
  420. ## * `lastPathPart proc <#lastPathPart,string>`_
  421. ## * `changeFileExt proc <#changeFileExt,string,string>`_
  422. ## * `addFileExt proc <#addFileExt,string,string>`_
  423. runnableExamples:
  424. assert searchExtPos("a/b/c") == -1
  425. assert searchExtPos("c.nim") == 1
  426. assert searchExtPos("a/b/c.nim") == 5
  427. assert searchExtPos("a.b.c.nim") == 5
  428. # BUGFIX: do not search until 0! .DS_Store is no file extension!
  429. result = -1
  430. for i in countdown(len(path)-1, 1):
  431. if path[i] == ExtSep:
  432. result = i
  433. break
  434. elif path[i] in {DirSep, AltSep}:
  435. break # do not skip over path
  436. proc splitFile*(path: string): tuple[dir, name, ext: string] {.
  437. noSideEffect, rtl, extern: "nos$1".} =
  438. ## Splits a filename into `(dir, name, extension)` tuple.
  439. ##
  440. ## `dir` does not end in `DirSep <#DirSep>`_.
  441. ## `extension` includes the leading dot.
  442. ##
  443. ## If `path` has no extension, `ext` is the empty string.
  444. ## If `path` has no directory component, `dir` is the empty string.
  445. ## If `path` has no filename component, `name` and `ext` are empty strings.
  446. ##
  447. ## See also:
  448. ## * `searchExtPos proc <#searchExtPos,string>`_
  449. ## * `extractFilename proc <#extractFilename,string>`_
  450. ## * `lastPathPart proc <#lastPathPart,string>`_
  451. ## * `changeFileExt proc <#changeFileExt,string,string>`_
  452. ## * `addFileExt proc <#addFileExt,string,string>`_
  453. runnableExamples:
  454. var (dir, name, ext) = splitFile("usr/local/nimc.html")
  455. assert dir == "usr/local"
  456. assert name == "nimc"
  457. assert ext == ".html"
  458. (dir, name, ext) = splitFile("/usr/local/os")
  459. assert dir == "/usr/local"
  460. assert name == "os"
  461. assert ext == ""
  462. (dir, name, ext) = splitFile("/usr/local/")
  463. assert dir == "/usr/local"
  464. assert name == ""
  465. assert ext == ""
  466. var namePos = 0
  467. var dotPos = 0
  468. for i in countdown(len(path) - 1, 0):
  469. if path[i] in {DirSep, AltSep} or i == 0:
  470. if path[i] in {DirSep, AltSep}:
  471. result.dir = substr(path, 0, max(0, i - 1))
  472. namePos = i + 1
  473. if dotPos > i:
  474. result.name = substr(path, namePos, dotPos - 1)
  475. result.ext = substr(path, dotPos)
  476. else:
  477. result.name = substr(path, namePos)
  478. break
  479. elif path[i] == ExtSep and i > 0 and i < len(path) - 1 and
  480. path[i - 1] notin {DirSep, AltSep} and
  481. path[i + 1] != ExtSep and dotPos == 0:
  482. dotPos = i
  483. proc extractFilename*(path: string): string {.
  484. noSideEffect, rtl, extern: "nos$1".} =
  485. ## Extracts the filename of a given `path`.
  486. ##
  487. ## This is the same as ``name & ext`` from `splitFile(path) proc
  488. ## <#splitFile,string>`_.
  489. ##
  490. ## See also:
  491. ## * `searchExtPos proc <#searchExtPos,string>`_
  492. ## * `splitFile proc <#splitFile,string>`_
  493. ## * `lastPathPart proc <#lastPathPart,string>`_
  494. ## * `changeFileExt proc <#changeFileExt,string,string>`_
  495. ## * `addFileExt proc <#addFileExt,string,string>`_
  496. runnableExamples:
  497. assert extractFilename("foo/bar/") == ""
  498. assert extractFilename("foo/bar") == "bar"
  499. assert extractFilename("foo/bar.baz") == "bar.baz"
  500. if path.len == 0 or path[path.len-1] in {DirSep, AltSep}:
  501. result = ""
  502. else:
  503. result = splitPath(path).tail
  504. proc lastPathPart*(path: string): string {.
  505. noSideEffect, rtl, extern: "nos$1".} =
  506. ## Like `extractFilename proc <#extractFilename,string>`_, but ignores
  507. ## trailing dir separator; aka: `baseName`:idx: in some other languages.
  508. ##
  509. ## See also:
  510. ## * `searchExtPos proc <#searchExtPos,string>`_
  511. ## * `splitFile proc <#splitFile,string>`_
  512. ## * `extractFilename proc <#extractFilename,string>`_
  513. ## * `changeFileExt proc <#changeFileExt,string,string>`_
  514. ## * `addFileExt proc <#addFileExt,string,string>`_
  515. runnableExamples:
  516. assert lastPathPart("foo/bar/") == "bar"
  517. assert lastPathPart("foo/bar") == "bar"
  518. let path = path.normalizePathEnd(trailingSep = false)
  519. result = extractFilename(path)
  520. proc changeFileExt*(filename, ext: string): string {.
  521. noSideEffect, rtl, extern: "nos$1".} =
  522. ## Changes the file extension to `ext`.
  523. ##
  524. ## If the `filename` has no extension, `ext` will be added.
  525. ## If `ext` == "" then any extension is removed.
  526. ##
  527. ## `Ext` should be given without the leading `'.'`, because some
  528. ## filesystems may use a different character. (Although I know
  529. ## of none such beast.)
  530. ##
  531. ## See also:
  532. ## * `searchExtPos proc <#searchExtPos,string>`_
  533. ## * `splitFile proc <#splitFile,string>`_
  534. ## * `extractFilename proc <#extractFilename,string>`_
  535. ## * `lastPathPart proc <#lastPathPart,string>`_
  536. ## * `addFileExt proc <#addFileExt,string,string>`_
  537. runnableExamples:
  538. assert changeFileExt("foo.bar", "baz") == "foo.baz"
  539. assert changeFileExt("foo.bar", "") == "foo"
  540. assert changeFileExt("foo", "baz") == "foo.baz"
  541. var extPos = searchExtPos(filename)
  542. if extPos < 0: result = filename & normExt(ext)
  543. else: result = substr(filename, 0, extPos-1) & normExt(ext)
  544. proc addFileExt*(filename, ext: string): string {.
  545. noSideEffect, rtl, extern: "nos$1".} =
  546. ## Adds the file extension `ext` to `filename`, unless
  547. ## `filename` already has an extension.
  548. ##
  549. ## `Ext` should be given without the leading `'.'`, because some
  550. ## filesystems may use a different character.
  551. ## (Although I know of none such beast.)
  552. ##
  553. ## See also:
  554. ## * `searchExtPos proc <#searchExtPos,string>`_
  555. ## * `splitFile proc <#splitFile,string>`_
  556. ## * `extractFilename proc <#extractFilename,string>`_
  557. ## * `lastPathPart proc <#lastPathPart,string>`_
  558. ## * `changeFileExt proc <#changeFileExt,string,string>`_
  559. runnableExamples:
  560. assert addFileExt("foo.bar", "baz") == "foo.bar"
  561. assert addFileExt("foo.bar", "") == "foo.bar"
  562. assert addFileExt("foo", "baz") == "foo.baz"
  563. var extPos = searchExtPos(filename)
  564. if extPos < 0: result = filename & normExt(ext)
  565. else: result = filename
  566. proc cmpPaths*(pathA, pathB: string): int {.
  567. noSideEffect, rtl, extern: "nos$1".} =
  568. ## Compares two paths.
  569. ##
  570. ## On a case-sensitive filesystem this is done
  571. ## case-sensitively otherwise case-insensitively. Returns:
  572. ##
  573. ## | 0 iff pathA == pathB
  574. ## | < 0 iff pathA < pathB
  575. ## | > 0 iff pathA > pathB
  576. runnableExamples:
  577. when defined(macosx):
  578. assert cmpPaths("foo", "Foo") == 0
  579. elif defined(posix):
  580. assert cmpPaths("foo", "Foo") > 0
  581. let a = normalizePath(pathA)
  582. let b = normalizePath(pathB)
  583. if FileSystemCaseSensitive:
  584. result = cmp(a, b)
  585. else:
  586. when defined(nimscript):
  587. result = cmpic(a, b)
  588. elif defined(nimdoc): discard
  589. else:
  590. result = cmpIgnoreCase(a, b)
  591. proc isAbsolute*(path: string): bool {.rtl, noSideEffect, extern: "nos$1".} =
  592. ## Checks whether a given `path` is absolute.
  593. ##
  594. ## On Windows, network paths are considered absolute too.
  595. runnableExamples:
  596. assert not "".isAbsolute
  597. assert not ".".isAbsolute
  598. when defined(posix):
  599. assert "/".isAbsolute
  600. assert not "a/".isAbsolute
  601. assert "/a/".isAbsolute
  602. if len(path) == 0: return false
  603. when doslikeFileSystem:
  604. var len = len(path)
  605. result = (path[0] in {'/', '\\'}) or
  606. (len > 1 and path[0] in {'a'..'z', 'A'..'Z'} and path[1] == ':')
  607. elif defined(macos):
  608. # according to https://perldoc.perl.org/File/Spec/Mac.html `:a` is a relative path
  609. result = path[0] != ':'
  610. elif defined(RISCOS):
  611. result = path[0] == '$'
  612. elif defined(posix):
  613. result = path[0] == '/'
  614. proc unixToNativePath*(path: string, drive=""): string {.
  615. noSideEffect, rtl, extern: "nos$1".} =
  616. ## Converts an UNIX-like path to a native one.
  617. ##
  618. ## On an UNIX system this does nothing. Else it converts
  619. ## `'/'`, `'.'`, `'..'` to the appropriate things.
  620. ##
  621. ## On systems with a concept of "drives", `drive` is used to determine
  622. ## which drive label to use during absolute path conversion.
  623. ## `drive` defaults to the drive of the current working directory, and is
  624. ## ignored on systems that do not have a concept of "drives".
  625. when defined(unix):
  626. result = path
  627. else:
  628. if path.len == 0: return ""
  629. var start: int
  630. if path[0] == '/':
  631. # an absolute path
  632. when doslikeFileSystem:
  633. if drive != "":
  634. result = drive & ":" & DirSep
  635. else:
  636. result = $DirSep
  637. elif defined(macos):
  638. result = "" # must not start with ':'
  639. else:
  640. result = $DirSep
  641. start = 1
  642. elif path[0] == '.' and (path.len == 1 or path[1] == '/'):
  643. # current directory
  644. result = $CurDir
  645. start = when doslikeFileSystem: 1 else: 2
  646. else:
  647. result = ""
  648. start = 0
  649. var i = start
  650. while i < len(path): # ../../../ --> ::::
  651. if i+2 < path.len and path[i] == '.' and path[i+1] == '.' and path[i+2] == '/':
  652. # parent directory
  653. when defined(macos):
  654. if result[high(result)] == ':':
  655. add result, ':'
  656. else:
  657. add result, ParDir
  658. else:
  659. add result, ParDir & DirSep
  660. inc(i, 3)
  661. elif path[i] == '/':
  662. add result, DirSep
  663. inc(i)
  664. else:
  665. add result, path[i]
  666. inc(i)
  667. include "includes/oserr"
  668. when not defined(nimscript):
  669. include "includes/osenv"
  670. proc getHomeDir*(): string {.rtl, extern: "nos$1",
  671. tags: [ReadEnvEffect, ReadIOEffect].} =
  672. ## Returns the home directory of the current user.
  673. ##
  674. ## This proc is wrapped by the `expandTilde proc <#expandTilde,string>`_
  675. ## for the convenience of processing paths coming from user configuration files.
  676. ##
  677. ## See also:
  678. ## * `getConfigDir proc <#getConfigDir>`_
  679. ## * `getTempDir proc <#getTempDir>`_
  680. ## * `expandTilde proc <#expandTilde,string>`_
  681. ## * `getCurrentDir proc <#getCurrentDir>`_
  682. ## * `setCurrentDir proc <#setCurrentDir,string>`_
  683. runnableExamples:
  684. assert getHomeDir() == expandTilde("~")
  685. when defined(windows): return string(getEnv("USERPROFILE")) & "\\"
  686. else: return string(getEnv("HOME")) & "/"
  687. proc getConfigDir*(): string {.rtl, extern: "nos$1",
  688. tags: [ReadEnvEffect, ReadIOEffect].} =
  689. ## Returns the config directory of the current user for applications.
  690. ##
  691. ## On non-Windows OSs, this proc conforms to the XDG Base Directory
  692. ## spec. Thus, this proc returns the value of the `XDG_CONFIG_HOME` environment
  693. ## variable if it is set, otherwise it returns the default configuration
  694. ## directory ("~/.config/").
  695. ##
  696. ## An OS-dependent trailing slash is always present at the end of the
  697. ## returned string: `\\` on Windows and `/` on all other OSs.
  698. ##
  699. ## See also:
  700. ## * `getHomeDir proc <#getHomeDir>`_
  701. ## * `getTempDir proc <#getTempDir>`_
  702. ## * `expandTilde proc <#expandTilde,string>`_
  703. ## * `getCurrentDir proc <#getCurrentDir>`_
  704. ## * `setCurrentDir proc <#setCurrentDir,string>`_
  705. when defined(windows):
  706. result = getEnv("APPDATA").string
  707. else:
  708. result = getEnv("XDG_CONFIG_HOME", getEnv("HOME").string / ".config").string
  709. result.normalizePathEnd(trailingSep = true)
  710. proc getTempDir*(): string {.rtl, extern: "nos$1",
  711. tags: [ReadEnvEffect, ReadIOEffect].} =
  712. ## Returns the temporary directory of the current user for applications to
  713. ## save temporary files in.
  714. ##
  715. ## **Please do not use this**: On Android, it currently
  716. ## returns ``getHomeDir()``, and on other Unix based systems it can cause
  717. ## security problems too. That said, you can override this implementation
  718. ## by adding ``-d:tempDir=mytempname`` to your compiler invocation.
  719. ##
  720. ## See also:
  721. ## * `getHomeDir proc <#getHomeDir>`_
  722. ## * `getConfigDir proc <#getConfigDir>`_
  723. ## * `expandTilde proc <#expandTilde,string>`_
  724. ## * `getCurrentDir proc <#getCurrentDir>`_
  725. ## * `setCurrentDir proc <#setCurrentDir,string>`_
  726. const tempDirDefault = "/tmp"
  727. result = tempDirDefault
  728. when defined(tempDir):
  729. const tempDir {.strdefine.}: string = tempDirDefault
  730. result = tempDir
  731. elif defined(windows): result = string(getEnv("TEMP"))
  732. elif defined(android): result = getHomeDir()
  733. else:
  734. if existsEnv("TMPDIR"): result = string(getEnv("TMPDIR"))
  735. normalizePathEnd(result, trailingSep=true)
  736. proc expandTilde*(path: string): string {.
  737. tags: [ReadEnvEffect, ReadIOEffect].} =
  738. ## Expands ``~`` or a path starting with ``~/`` to a full path, replacing
  739. ## ``~`` with `getHomeDir() <#getHomeDir>`_ (otherwise returns ``path`` unmodified).
  740. ##
  741. ## Windows: this is still supported despite Windows platform not having this
  742. ## convention; also, both ``~/`` and ``~\`` are handled.
  743. ##
  744. ## See also:
  745. ## * `getHomeDir proc <#getHomeDir>`_
  746. ## * `getConfigDir proc <#getConfigDir>`_
  747. ## * `getTempDir proc <#getTempDir>`_
  748. ## * `getCurrentDir proc <#getCurrentDir>`_
  749. ## * `setCurrentDir proc <#setCurrentDir,string>`_
  750. runnableExamples:
  751. assert expandTilde("~" / "appname.cfg") == getHomeDir() / "appname.cfg"
  752. assert expandTilde("~/foo/bar") == getHomeDir() / "foo/bar"
  753. assert expandTilde("/foo/bar") == "/foo/bar"
  754. if len(path) == 0 or path[0] != '~':
  755. result = path
  756. elif len(path) == 1:
  757. result = getHomeDir()
  758. elif (path[1] in {DirSep, AltSep}):
  759. result = getHomeDir() / path.substr(2)
  760. else:
  761. # TODO: handle `~bob` and `~bob/` which means home of bob
  762. result = path
  763. # TODO: consider whether quoteShellPosix, quoteShellWindows, quoteShell, quoteShellCommand
  764. # belong in `strutils` instead; they are not specific to paths
  765. proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  766. ## Quote `s`, so it can be safely passed to Windows API.
  767. ##
  768. ## Based on Python's `subprocess.list2cmdline`.
  769. ## See `this link <http://msdn.microsoft.com/en-us/library/17w5ykft.aspx>`_
  770. ## for more details.
  771. let needQuote = {' ', '\t'} in s or s.len == 0
  772. result = ""
  773. var backslashBuff = ""
  774. if needQuote:
  775. result.add("\"")
  776. for c in s:
  777. if c == '\\':
  778. backslashBuff.add(c)
  779. elif c == '\"':
  780. result.add(backslashBuff)
  781. result.add(backslashBuff)
  782. backslashBuff.setLen(0)
  783. result.add("\\\"")
  784. else:
  785. if backslashBuff.len != 0:
  786. result.add(backslashBuff)
  787. backslashBuff.setLen(0)
  788. result.add(c)
  789. if needQuote:
  790. result.add("\"")
  791. proc quoteShellPosix*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  792. ## Quote ``s``, so it can be safely passed to POSIX shell.
  793. ## Based on Python's `pipes.quote`.
  794. const safeUnixChars = {'%', '+', '-', '.', '/', '_', ':', '=', '@',
  795. '0'..'9', 'A'..'Z', 'a'..'z'}
  796. if s.len == 0:
  797. return "''"
  798. let safe = s.allCharsInSet(safeUnixChars)
  799. if safe:
  800. return s
  801. else:
  802. return "'" & s.replace("'", "'\"'\"'") & "'"
  803. when defined(windows) or defined(posix) or defined(nintendoswitch):
  804. proc quoteShell*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} =
  805. ## Quote ``s``, so it can be safely passed to shell.
  806. ##
  807. ## When on Windows, it calls `quoteShellWindows proc
  808. ## <#quoteShellWindows,string>`_. Otherwise, calls `quoteShellPosix proc
  809. ## <#quoteShellPosix,string>`_.
  810. when defined(windows):
  811. return quoteShellWindows(s)
  812. else:
  813. return quoteShellPosix(s)
  814. proc quoteShellCommand*(args: openArray[string]): string =
  815. ## Concatenates and quotes shell arguments `args`.
  816. runnableExamples:
  817. when defined(posix):
  818. assert quoteShellCommand(["aaa", "", "c d"]) == "aaa '' 'c d'"
  819. when defined(windows):
  820. assert quoteShellCommand(["aaa", "", "c d"]) == "aaa \"\" \"c d\""
  821. # can't use `map` pending https://github.com/nim-lang/Nim/issues/8303
  822. for i in 0..<args.len:
  823. if i > 0: result.add " "
  824. result.add quoteShell(args[i])
  825. when not weirdTarget:
  826. proc c_rename(oldname, newname: cstring): cint {.
  827. importc: "rename", header: "<stdio.h>".}
  828. proc c_system(cmd: cstring): cint {.
  829. importc: "system", header: "<stdlib.h>".}
  830. proc c_strlen(a: cstring): cint {.
  831. importc: "strlen", header: "<string.h>", noSideEffect.}
  832. proc c_free(p: pointer) {.
  833. importc: "free", header: "<stdlib.h>".}
  834. when defined(windows) and not weirdTarget:
  835. when useWinUnicode:
  836. template wrapUnary(varname, winApiProc, arg: untyped) =
  837. var varname = winApiProc(newWideCString(arg))
  838. template wrapBinary(varname, winApiProc, arg, arg2: untyped) =
  839. var varname = winApiProc(newWideCString(arg), arg2)
  840. proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle =
  841. result = findFirstFileW(newWideCString(a), b)
  842. template findNextFile(a, b: untyped): untyped = findNextFileW(a, b)
  843. template getCommandLine(): untyped = getCommandLineW()
  844. template getFilename(f: untyped): untyped =
  845. $cast[WideCString](addr(f.cFileName[0]))
  846. else:
  847. template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b)
  848. template findNextFile(a, b: untyped): untyped = findNextFileA(a, b)
  849. template getCommandLine(): untyped = getCommandLineA()
  850. template getFilename(f: untyped): untyped = $f.cFileName
  851. proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} =
  852. # Note - takes advantage of null delimiter in the cstring
  853. const dot = ord('.')
  854. result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or
  855. f.cFileName[1].int == dot and f.cFileName[2].int == 0)
  856. proc existsFile*(filename: string): bool {.rtl, extern: "nos$1",
  857. tags: [ReadDirEffect], noNimScript.} =
  858. ## Returns true if `filename` exists and is a regular file or symlink.
  859. ##
  860. ## Directories, device files, named pipes and sockets return false.
  861. ##
  862. ## See also:
  863. ## * `existsDir proc <#existsDir,string>`_
  864. ## * `symlinkExists proc <#symlinkExists,string>`_
  865. when defined(windows):
  866. when useWinUnicode:
  867. wrapUnary(a, getFileAttributesW, filename)
  868. else:
  869. var a = getFileAttributesA(filename)
  870. if a != -1'i32:
  871. result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32
  872. else:
  873. var res: Stat
  874. return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode)
  875. proc existsDir*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect],
  876. noNimScript.} =
  877. ## Returns true iff the directory `dir` exists. If `dir` is a file, false
  878. ## is returned. Follows symlinks.
  879. ##
  880. ## See also:
  881. ## * `existsFile proc <#existsFile,string>`_
  882. ## * `symlinkExists proc <#symlinkExists,string>`_
  883. when defined(windows):
  884. when useWinUnicode:
  885. wrapUnary(a, getFileAttributesW, dir)
  886. else:
  887. var a = getFileAttributesA(dir)
  888. if a != -1'i32:
  889. result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  890. else:
  891. var res: Stat
  892. return stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode)
  893. proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1",
  894. tags: [ReadDirEffect],
  895. noNimScript.} =
  896. ## Returns true iff the symlink `link` exists. Will return true
  897. ## regardless of whether the link points to a directory or file.
  898. ##
  899. ## See also:
  900. ## * `existsFile proc <#existsFile,string>`_
  901. ## * `existsDir proc <#existsDir,string>`_
  902. when defined(windows):
  903. when useWinUnicode:
  904. wrapUnary(a, getFileAttributesW, link)
  905. else:
  906. var a = getFileAttributesA(link)
  907. if a != -1'i32:
  908. result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32
  909. else:
  910. var res: Stat
  911. return lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode)
  912. proc fileExists*(filename: string): bool {.inline, noNimScript.} =
  913. ## Alias for `existsFile proc <#existsFile,string>`_.
  914. ##
  915. ## See also:
  916. ## * `existsDir proc <#existsDir,string>`_
  917. ## * `symlinkExists proc <#symlinkExists,string>`_
  918. existsFile(filename)
  919. proc dirExists*(dir: string): bool {.inline, noNimScript.} =
  920. ## Alias for `existsDir proc <#existsDir,string>`_.
  921. ##
  922. ## See also:
  923. ## * `existsFile proc <#existsFile,string>`_
  924. ## * `symlinkExists proc <#symlinkExists,string>`_
  925. existsDir(dir)
  926. when not defined(windows) and not weirdTarget:
  927. proc checkSymlink(path: string): bool =
  928. var rawInfo: Stat
  929. if lstat(path, rawInfo) < 0'i32: result = false
  930. else: result = S_ISLNK(rawInfo.st_mode)
  931. const
  932. ExeExts* = ## Platform specific file extension for executables.
  933. ## On Windows ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
  934. when defined(windows): ["exe", "cmd", "bat"] else: [""]
  935. proc findExe*(exe: string, followSymlinks: bool = true;
  936. extensions: openArray[string]=ExeExts): string {.
  937. tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimScript.} =
  938. ## Searches for `exe` in the current working directory and then
  939. ## in directories listed in the ``PATH`` environment variable.
  940. ##
  941. ## Returns `""` if the `exe` cannot be found. `exe`
  942. ## is added the `ExeExts <#ExeExts>`_ file extensions if it has none.
  943. ##
  944. ## If the system supports symlinks it also resolves them until it
  945. ## meets the actual file. This behavior can be disabled if desired
  946. ## by setting `followSymlinks = false`.
  947. if exe.len == 0: return
  948. template checkCurrentDir() =
  949. for ext in extensions:
  950. result = addFileExt(exe, ext)
  951. if existsFile(result): return
  952. when defined(posix):
  953. if '/' in exe: checkCurrentDir()
  954. else:
  955. checkCurrentDir()
  956. let path = string(getEnv("PATH"))
  957. for candidate in split(path, PathSep):
  958. if candidate.len == 0: continue
  959. when defined(windows):
  960. var x = (if candidate[0] == '"' and candidate[^1] == '"':
  961. substr(candidate, 1, candidate.len-2) else: candidate) /
  962. exe
  963. else:
  964. var x = expandTilde(candidate) / exe
  965. for ext in extensions:
  966. var x = addFileExt(x, ext)
  967. if existsFile(x):
  968. when not defined(windows):
  969. while followSymlinks: # doubles as if here
  970. if x.checkSymlink:
  971. var r = newString(256)
  972. var len = readlink(x, r, 256)
  973. if len < 0:
  974. raiseOSError(osLastError())
  975. if len > 256:
  976. r = newString(len+1)
  977. len = readlink(x, r, len)
  978. setLen(r, len)
  979. if isAbsolute(r):
  980. x = r
  981. else:
  982. x = parentDir(x) / r
  983. else:
  984. break
  985. return x
  986. result = ""
  987. when weirdTarget:
  988. const times = "fake const"
  989. template Time(x: untyped): untyped = string
  990. proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} =
  991. ## Returns the `file`'s last modification time.
  992. ##
  993. ## See also:
  994. ## * `getLastAccessTime proc <#getLastAccessTime,string>`_
  995. ## * `getCreationTime proc <#getCreationTime,string>`_
  996. ## * `fileNewer proc <#fileNewer,string,string>`_
  997. when defined(posix):
  998. var res: Stat
  999. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  1000. result = res.st_mtim.toTime
  1001. else:
  1002. var f: WIN32_FIND_DATA
  1003. var h = findFirstFile(file, f)
  1004. if h == -1'i32: raiseOSError(osLastError())
  1005. result = fromWinTime(rdFileTime(f.ftLastWriteTime))
  1006. findClose(h)
  1007. proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} =
  1008. ## Returns the `file`'s last read or write access time.
  1009. ##
  1010. ## See also:
  1011. ## * `getLastModificationTime proc <#getLastModificationTime,string>`_
  1012. ## * `getCreationTime proc <#getCreationTime,string>`_
  1013. ## * `fileNewer proc <#fileNewer,string,string>`_
  1014. when defined(posix):
  1015. var res: Stat
  1016. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  1017. result = res.st_atim.toTime
  1018. else:
  1019. var f: WIN32_FIND_DATA
  1020. var h = findFirstFile(file, f)
  1021. if h == -1'i32: raiseOSError(osLastError())
  1022. result = fromWinTime(rdFileTime(f.ftLastAccessTime))
  1023. findClose(h)
  1024. proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noNimScript.} =
  1025. ## Returns the `file`'s creation time.
  1026. ##
  1027. ## **Note:** Under POSIX OS's, the returned time may actually be the time at
  1028. ## which the file's attribute's were last modified. See
  1029. ## `here <https://github.com/nim-lang/Nim/issues/1058>`_ for details.
  1030. ##
  1031. ## See also:
  1032. ## * `getLastModificationTime proc <#getLastModificationTime,string>`_
  1033. ## * `getLastAccessTime proc <#getLastAccessTime,string>`_
  1034. ## * `fileNewer proc <#fileNewer,string,string>`_
  1035. when defined(posix):
  1036. var res: Stat
  1037. if stat(file, res) < 0'i32: raiseOSError(osLastError())
  1038. result = res.st_ctim.toTime
  1039. else:
  1040. var f: WIN32_FIND_DATA
  1041. var h = findFirstFile(file, f)
  1042. if h == -1'i32: raiseOSError(osLastError())
  1043. result = fromWinTime(rdFileTime(f.ftCreationTime))
  1044. findClose(h)
  1045. proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noNimScript.} =
  1046. ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s
  1047. ## modification time is later than `b`'s.
  1048. ##
  1049. ## See also:
  1050. ## * `getLastModificationTime proc <#getLastModificationTime,string>`_
  1051. ## * `getLastAccessTime proc <#getLastAccessTime,string>`_
  1052. ## * `getCreationTime proc <#getCreationTime,string>`_
  1053. when defined(posix):
  1054. # If we don't have access to nanosecond resolution, use '>='
  1055. when not StatHasNanoseconds:
  1056. result = getLastModificationTime(a) >= getLastModificationTime(b)
  1057. else:
  1058. result = getLastModificationTime(a) > getLastModificationTime(b)
  1059. else:
  1060. result = getLastModificationTime(a) > getLastModificationTime(b)
  1061. proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [], noNimScript.} =
  1062. ## Returns the `current working directory`:idx: i.e. where the built
  1063. ## binary is run.
  1064. ##
  1065. ## So the path returned by this proc is determined at run time.
  1066. ##
  1067. ## See also:
  1068. ## * `getHomeDir proc <#getHomeDir>`_
  1069. ## * `getConfigDir proc <#getConfigDir>`_
  1070. ## * `getTempDir proc <#getTempDir>`_
  1071. ## * `setCurrentDir proc <#setCurrentDir,string>`_
  1072. ## * `currentSourcePath template <system.html#currentSourcePath.t>`_
  1073. ## * `getProjectPath proc <macros.html#getProjectPath>`_
  1074. when defined(windows):
  1075. var bufsize = MAX_PATH.int32
  1076. when useWinUnicode:
  1077. var res = newWideCString("", bufsize)
  1078. while true:
  1079. var L = getCurrentDirectoryW(bufsize, res)
  1080. if L == 0'i32:
  1081. raiseOSError(osLastError())
  1082. elif L > bufsize:
  1083. res = newWideCString("", L)
  1084. bufsize = L
  1085. else:
  1086. result = res$L
  1087. break
  1088. else:
  1089. result = newString(bufsize)
  1090. while true:
  1091. var L = getCurrentDirectoryA(bufsize, result)
  1092. if L == 0'i32:
  1093. raiseOSError(osLastError())
  1094. elif L > bufsize:
  1095. result = newString(L)
  1096. bufsize = L
  1097. else:
  1098. setLen(result, L)
  1099. break
  1100. else:
  1101. var bufsize = 1024 # should be enough
  1102. result = newString(bufsize)
  1103. while true:
  1104. if getcwd(result, bufsize) != nil:
  1105. setLen(result, c_strlen(result))
  1106. break
  1107. else:
  1108. let err = osLastError()
  1109. if err.int32 == ERANGE:
  1110. bufsize = bufsize shl 1
  1111. doAssert(bufsize >= 0)
  1112. result = newString(bufsize)
  1113. else:
  1114. raiseOSError(osLastError())
  1115. proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} =
  1116. ## Sets the `current working directory`:idx:; `OSError`
  1117. ## is raised if `newDir` cannot been set.
  1118. ##
  1119. ## See also:
  1120. ## * `getHomeDir proc <#getHomeDir>`_
  1121. ## * `getConfigDir proc <#getConfigDir>`_
  1122. ## * `getTempDir proc <#getTempDir>`_
  1123. ## * `getCurrentDir proc <#getCurrentDir>`_
  1124. when defined(Windows):
  1125. when useWinUnicode:
  1126. if setCurrentDirectoryW(newWideCString(newDir)) == 0'i32:
  1127. raiseOSError(osLastError())
  1128. else:
  1129. if setCurrentDirectoryA(newDir) == 0'i32: raiseOSError(osLastError())
  1130. else:
  1131. if chdir(newDir) != 0'i32: raiseOSError(osLastError())
  1132. when not weirdTarget:
  1133. proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} =
  1134. ## Returns the absolute path of `path`, rooted at `root` (which must be absolute;
  1135. ## default: current directory).
  1136. ## If `path` is absolute, return it, ignoring `root`.
  1137. ##
  1138. ## See also:
  1139. ## * `normalizedPath proc <#normalizedPath,string>`_
  1140. ## * `normalizePath proc <#normalizePath,string>`_
  1141. runnableExamples:
  1142. assert absolutePath("a") == getCurrentDir() / "a"
  1143. if isAbsolute(path): path
  1144. else:
  1145. if not root.isAbsolute:
  1146. raise newException(ValueError, "The specified root is not absolute: " & root)
  1147. joinPath(root, path)
  1148. proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScript.} =
  1149. ## Normalize a path.
  1150. ##
  1151. ## Consecutive directory separators are collapsed, including an initial double slash.
  1152. ##
  1153. ## On relative paths, double dot (`..`) sequences are collapsed if possible.
  1154. ## On absolute paths they are always collapsed.
  1155. ##
  1156. ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected.
  1157. ## Triple dot is not handled.
  1158. ##
  1159. ## See also:
  1160. ## * `absolutePath proc <#absolutePath,string>`_
  1161. ## * `normalizedPath proc <#normalizedPath,string>`_ for a version which returns
  1162. ## a new string
  1163. runnableExamples:
  1164. when defined(posix):
  1165. var a = "a///b//..//c///d"
  1166. a.normalizePath()
  1167. assert a == "a/c/d"
  1168. path = pathnorm.normalizePath(path)
  1169. when false:
  1170. let isAbs = isAbsolute(path)
  1171. var stack: seq[string] = @[]
  1172. for p in split(path, {DirSep}):
  1173. case p
  1174. of "", ".":
  1175. continue
  1176. of "..":
  1177. if stack.len == 0:
  1178. if isAbs:
  1179. discard # collapse all double dots on absoluta paths
  1180. else:
  1181. stack.add(p)
  1182. elif stack[^1] == "..":
  1183. stack.add(p)
  1184. else:
  1185. discard stack.pop()
  1186. else:
  1187. stack.add(p)
  1188. if isAbs:
  1189. path = DirSep & join(stack, $DirSep)
  1190. elif stack.len > 0:
  1191. path = join(stack, $DirSep)
  1192. else:
  1193. path = "."
  1194. proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} =
  1195. ## Returns a normalized path for the current OS.
  1196. ##
  1197. ## See also:
  1198. ## * `absolutePath proc <#absolutePath,string>`_
  1199. ## * `normalizePath proc <#normalizePath,string>`_ for the in-place version
  1200. runnableExamples:
  1201. when defined(posix):
  1202. assert normalizedPath("a///b//..//c///d") == "a/c/d"
  1203. result = pathnorm.normalizePath(path)
  1204. when defined(Windows) and not weirdTarget:
  1205. proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle =
  1206. var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL
  1207. if not followSymlink:
  1208. flags = flags or FILE_FLAG_OPEN_REPARSE_POINT
  1209. let access = if writeAccess: GENERIC_WRITE else: 0'i32
  1210. when useWinUnicode:
  1211. result = createFileW(
  1212. newWideCString(path), access,
  1213. FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
  1214. nil, OPEN_EXISTING, flags, 0
  1215. )
  1216. else:
  1217. result = createFileA(
  1218. path, access,
  1219. FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE,
  1220. nil, OPEN_EXISTING, flags, 0
  1221. )
  1222. proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1",
  1223. tags: [ReadDirEffect], noNimScript.} =
  1224. ## Returns true if both pathname arguments refer to the same physical
  1225. ## file or directory.
  1226. ##
  1227. ## Raises `OSError` if any of the files does not
  1228. ## exist or information about it can not be obtained.
  1229. ##
  1230. ## This proc will return true if given two alternative hard-linked or
  1231. ## sym-linked paths to the same file or directory.
  1232. ##
  1233. ## See also:
  1234. ## * `sameFileContent proc <#sameFileContent,string,string>`_
  1235. when defined(Windows):
  1236. var success = true
  1237. var f1 = openHandle(path1)
  1238. var f2 = openHandle(path2)
  1239. var lastErr: OSErrorCode
  1240. if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE:
  1241. var fi1, fi2: BY_HANDLE_FILE_INFORMATION
  1242. if getFileInformationByHandle(f1, addr(fi1)) != 0 and
  1243. getFileInformationByHandle(f2, addr(fi2)) != 0:
  1244. result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and
  1245. fi1.nFileIndexHigh == fi2.nFileIndexHigh and
  1246. fi1.nFileIndexLow == fi2.nFileIndexLow
  1247. else:
  1248. lastErr = osLastError()
  1249. success = false
  1250. else:
  1251. lastErr = osLastError()
  1252. success = false
  1253. discard closeHandle(f1)
  1254. discard closeHandle(f2)
  1255. if not success: raiseOSError(lastErr)
  1256. else:
  1257. var a, b: Stat
  1258. if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32:
  1259. raiseOSError(osLastError())
  1260. else:
  1261. result = a.st_dev == b.st_dev and a.st_ino == b.st_ino
  1262. proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1",
  1263. tags: [ReadIOEffect], noNimScript.} =
  1264. ## Returns true if both pathname arguments refer to files with identical
  1265. ## binary content.
  1266. ##
  1267. ## See also:
  1268. ## * `sameFile proc <#sameFile,string,string>`_
  1269. const
  1270. bufSize = 8192 # 8K buffer
  1271. var
  1272. a, b: File
  1273. if not open(a, path1): return false
  1274. if not open(b, path2):
  1275. close(a)
  1276. return false
  1277. var bufA = alloc(bufSize)
  1278. var bufB = alloc(bufSize)
  1279. while true:
  1280. var readA = readBuffer(a, bufA, bufSize)
  1281. var readB = readBuffer(b, bufB, bufSize)
  1282. if readA != readB:
  1283. result = false
  1284. break
  1285. if readA == 0:
  1286. result = true
  1287. break
  1288. result = equalMem(bufA, bufB, readA)
  1289. if not result: break
  1290. if readA != bufSize: break # end of file
  1291. dealloc(bufA)
  1292. dealloc(bufB)
  1293. close(a)
  1294. close(b)
  1295. type
  1296. FilePermission* = enum ## File access permission, modelled after UNIX.
  1297. ##
  1298. ## See also:
  1299. ## * `getFilePermissions <#getFilePermissions,string>`_
  1300. ## * `setFilePermissions <#setFilePermissions,string,set[FilePermission]>`_
  1301. ## * `FileInfo object <#FileInfo>`_
  1302. fpUserExec, ## execute access for the file owner
  1303. fpUserWrite, ## write access for the file owner
  1304. fpUserRead, ## read access for the file owner
  1305. fpGroupExec, ## execute access for the group
  1306. fpGroupWrite, ## write access for the group
  1307. fpGroupRead, ## read access for the group
  1308. fpOthersExec, ## execute access for others
  1309. fpOthersWrite, ## write access for others
  1310. fpOthersRead ## read access for others
  1311. proc getFilePermissions*(filename: string): set[FilePermission] {.
  1312. rtl, extern: "nos$1", tags: [ReadDirEffect], noNimScript.} =
  1313. ## Retrieves file permissions for `filename`.
  1314. ##
  1315. ## `OSError` is raised in case of an error.
  1316. ## On Windows, only the ``readonly`` flag is checked, every other
  1317. ## permission is available in any case.
  1318. ##
  1319. ## See also:
  1320. ## * `setFilePermissions proc <#setFilePermissions,string,set[FilePermission]>`_
  1321. ## * `FilePermission enum <#FilePermission>`_
  1322. when defined(posix):
  1323. var a: Stat
  1324. if stat(filename, a) < 0'i32: raiseOSError(osLastError())
  1325. result = {}
  1326. if (a.st_mode and S_IRUSR.Mode) != 0.Mode: result.incl(fpUserRead)
  1327. if (a.st_mode and S_IWUSR.Mode) != 0.Mode: result.incl(fpUserWrite)
  1328. if (a.st_mode and S_IXUSR.Mode) != 0.Mode: result.incl(fpUserExec)
  1329. if (a.st_mode and S_IRGRP.Mode) != 0.Mode: result.incl(fpGroupRead)
  1330. if (a.st_mode and S_IWGRP.Mode) != 0.Mode: result.incl(fpGroupWrite)
  1331. if (a.st_mode and S_IXGRP.Mode) != 0.Mode: result.incl(fpGroupExec)
  1332. if (a.st_mode and S_IROTH.Mode) != 0.Mode: result.incl(fpOthersRead)
  1333. if (a.st_mode and S_IWOTH.Mode) != 0.Mode: result.incl(fpOthersWrite)
  1334. if (a.st_mode and S_IXOTH.Mode) != 0.Mode: result.incl(fpOthersExec)
  1335. else:
  1336. when useWinUnicode:
  1337. wrapUnary(res, getFileAttributesW, filename)
  1338. else:
  1339. var res = getFileAttributesA(filename)
  1340. if res == -1'i32: raiseOSError(osLastError())
  1341. if (res and FILE_ATTRIBUTE_READONLY) != 0'i32:
  1342. result = {fpUserExec, fpUserRead, fpGroupExec, fpGroupRead,
  1343. fpOthersExec, fpOthersRead}
  1344. else:
  1345. result = {fpUserExec..fpOthersRead}
  1346. proc setFilePermissions*(filename: string, permissions: set[FilePermission]) {.
  1347. rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} =
  1348. ## Sets the file permissions for `filename`.
  1349. ##
  1350. ## `OSError` is raised in case of an error.
  1351. ## On Windows, only the ``readonly`` flag is changed, depending on
  1352. ## ``fpUserWrite`` permission.
  1353. ##
  1354. ## See also:
  1355. ## * `getFilePermissions <#getFilePermissions,string>`_
  1356. ## * `FilePermission enum <#FilePermission>`_
  1357. when defined(posix):
  1358. var p = 0.Mode
  1359. if fpUserRead in permissions: p = p or S_IRUSR.Mode
  1360. if fpUserWrite in permissions: p = p or S_IWUSR.Mode
  1361. if fpUserExec in permissions: p = p or S_IXUSR.Mode
  1362. if fpGroupRead in permissions: p = p or S_IRGRP.Mode
  1363. if fpGroupWrite in permissions: p = p or S_IWGRP.Mode
  1364. if fpGroupExec in permissions: p = p or S_IXGRP.Mode
  1365. if fpOthersRead in permissions: p = p or S_IROTH.Mode
  1366. if fpOthersWrite in permissions: p = p or S_IWOTH.Mode
  1367. if fpOthersExec in permissions: p = p or S_IXOTH.Mode
  1368. if chmod(filename, cast[Mode](p)) != 0: raiseOSError(osLastError())
  1369. else:
  1370. when useWinUnicode:
  1371. wrapUnary(res, getFileAttributesW, filename)
  1372. else:
  1373. var res = getFileAttributesA(filename)
  1374. if res == -1'i32: raiseOSError(osLastError())
  1375. if fpUserWrite in permissions:
  1376. res = res and not FILE_ATTRIBUTE_READONLY
  1377. else:
  1378. res = res or FILE_ATTRIBUTE_READONLY
  1379. when useWinUnicode:
  1380. wrapBinary(res2, setFileAttributesW, filename, res)
  1381. else:
  1382. var res2 = setFileAttributesA(filename, res)
  1383. if res2 == - 1'i32: raiseOSError(osLastError())
  1384. proc copyFile*(source, dest: string) {.rtl, extern: "nos$1",
  1385. tags: [ReadIOEffect, WriteIOEffect], noNimScript.} =
  1386. ## Copies a file from `source` to `dest`.
  1387. ##
  1388. ## If this fails, `OSError` is raised.
  1389. ##
  1390. ## On the Windows platform this proc will
  1391. ## copy the source file's attributes into dest.
  1392. ##
  1393. ## On other platforms you need
  1394. ## to use `getFilePermissions <#getFilePermissions,string>`_ and
  1395. ## `setFilePermissions <#setFilePermissions,string,set[FilePermission]>`_ procs
  1396. ## to copy them by hand (or use the convenience `copyFileWithPermissions
  1397. ## proc <#copyFileWithPermissions,string,string>`_),
  1398. ## otherwise `dest` will inherit the default permissions of a newly
  1399. ## created file for the user.
  1400. ##
  1401. ## If `dest` already exists, the file attributes
  1402. ## will be preserved and the content overwritten.
  1403. ##
  1404. ## See also:
  1405. ## * `copyDir proc <#copyDir,string,string>`_
  1406. ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_
  1407. ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
  1408. ## * `removeFile proc <#removeFile,string>`_
  1409. ## * `moveFile proc <#moveFile,string,string>`_
  1410. when defined(Windows):
  1411. when useWinUnicode:
  1412. let s = newWideCString(source)
  1413. let d = newWideCString(dest)
  1414. if copyFileW(s, d, 0'i32) == 0'i32: raiseOSError(osLastError())
  1415. else:
  1416. if copyFileA(source, dest, 0'i32) == 0'i32: raiseOSError(osLastError())
  1417. else:
  1418. # generic version of copyFile which works for any platform:
  1419. const bufSize = 8000 # better for memory manager
  1420. var d, s: File
  1421. if not open(s, source): raiseOSError(osLastError())
  1422. if not open(d, dest, fmWrite):
  1423. close(s)
  1424. raiseOSError(osLastError())
  1425. var buf = alloc(bufSize)
  1426. while true:
  1427. var bytesread = readBuffer(s, buf, bufSize)
  1428. if bytesread > 0:
  1429. var byteswritten = writeBuffer(d, buf, bytesread)
  1430. if bytesread != byteswritten:
  1431. dealloc(buf)
  1432. close(s)
  1433. close(d)
  1434. raiseOSError(osLastError())
  1435. if bytesread != bufSize: break
  1436. dealloc(buf)
  1437. close(s)
  1438. flushFile(d)
  1439. close(d)
  1440. when not declared(ENOENT) and not defined(Windows):
  1441. when NoFakeVars:
  1442. when not defined(haiku):
  1443. const ENOENT = cint(2) # 2 on most systems including Solaris
  1444. else:
  1445. const ENOENT = cint(-2147459069)
  1446. else:
  1447. var ENOENT {.importc, header: "<errno.h>".}: cint
  1448. when defined(Windows) and not weirdTarget:
  1449. when useWinUnicode:
  1450. template deleteFile(file: untyped): untyped = deleteFileW(file)
  1451. template setFileAttributes(file, attrs: untyped): untyped =
  1452. setFileAttributesW(file, attrs)
  1453. else:
  1454. template deleteFile(file: untyped): untyped = deleteFileA(file)
  1455. template setFileAttributes(file, attrs: untyped): untyped =
  1456. setFileAttributesA(file, attrs)
  1457. proc tryRemoveFile*(file: string): bool {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} =
  1458. ## Removes the `file`.
  1459. ##
  1460. ## If this fails, returns `false`. This does not fail
  1461. ## if the file never existed in the first place.
  1462. ##
  1463. ## On Windows, ignores the read-only attribute.
  1464. ##
  1465. ## See also:
  1466. ## * `copyFile proc <#copyFile,string,string>`_
  1467. ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_
  1468. ## * `removeFile proc <#removeFile,string>`_
  1469. ## * `moveFile proc <#moveFile,string,string>`_
  1470. result = true
  1471. when defined(Windows):
  1472. when useWinUnicode:
  1473. let f = newWideCString(file)
  1474. else:
  1475. let f = file
  1476. if deleteFile(f) == 0:
  1477. result = false
  1478. let err = getLastError()
  1479. if err == ERROR_FILE_NOT_FOUND or err == ERROR_PATH_NOT_FOUND:
  1480. result = true
  1481. elif err == ERROR_ACCESS_DENIED and
  1482. setFileAttributes(f, FILE_ATTRIBUTE_NORMAL) != 0 and
  1483. deleteFile(f) != 0:
  1484. result = true
  1485. else:
  1486. if unlink(file) != 0'i32 and errno != ENOENT:
  1487. result = false
  1488. proc removeFile*(file: string) {.rtl, extern: "nos$1", tags: [WriteDirEffect], noNimScript.} =
  1489. ## Removes the `file`.
  1490. ##
  1491. ## If this fails, `OSError` is raised. This does not fail
  1492. ## if the file never existed in the first place.
  1493. ##
  1494. ## On Windows, ignores the read-only attribute.
  1495. ##
  1496. ## See also:
  1497. ## * `removeDir proc <#removeDir,string>`_
  1498. ## * `copyFile proc <#copyFile,string,string>`_
  1499. ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_
  1500. ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
  1501. ## * `moveFile proc <#moveFile,string,string>`_
  1502. if not tryRemoveFile(file):
  1503. when defined(Windows):
  1504. raiseOSError(osLastError())
  1505. else:
  1506. raiseOSError(osLastError(), $strerror(errno))
  1507. proc tryMoveFSObject(source, dest: string): bool {.noNimScript.} =
  1508. ## Moves a file or directory from `source` to `dest`.
  1509. ##
  1510. ## Returns false in case of `EXDEV` error.
  1511. ## In case of other errors `OSError` is raised.
  1512. ## Returns true in case of success.
  1513. when defined(Windows):
  1514. when useWinUnicode:
  1515. let s = newWideCString(source)
  1516. let d = newWideCString(dest)
  1517. if moveFileExW(s, d, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
  1518. else:
  1519. if moveFileExA(source, dest, MOVEFILE_COPY_ALLOWED) == 0'i32: raiseOSError(osLastError())
  1520. else:
  1521. if c_rename(source, dest) != 0'i32:
  1522. let err = osLastError()
  1523. if err == EXDEV.OSErrorCode:
  1524. return false
  1525. else:
  1526. raiseOSError(err, $strerror(errno))
  1527. return true
  1528. proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
  1529. tags: [ReadIOEffect, WriteIOEffect], noNimScript.} =
  1530. ## Moves a file from `source` to `dest`.
  1531. ##
  1532. ## If this fails, `OSError` is raised.
  1533. ## If `dest` already exists, it will be overwritten.
  1534. ##
  1535. ## Can be used to `rename files`:idx:.
  1536. ##
  1537. ## See also:
  1538. ## * `moveDir proc <#moveDir,string,string>`_
  1539. ## * `copyFile proc <#copyFile,string,string>`_
  1540. ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_
  1541. ## * `removeFile proc <#removeFile,string>`_
  1542. ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
  1543. if not tryMoveFSObject(source, dest):
  1544. when not defined(windows):
  1545. # Fallback to copy & del
  1546. copyFile(source, dest)
  1547. try:
  1548. removeFile(source)
  1549. except:
  1550. discard tryRemoveFile(dest)
  1551. raise
  1552. proc exitStatusLikeShell*(status: cint): cint =
  1553. ## Converts exit code from `c_system` into a shell exit code.
  1554. when defined(posix) and not weirdTarget:
  1555. if WIFSIGNALED(status):
  1556. # like the shell!
  1557. 128 + WTERMSIG(status)
  1558. else:
  1559. WEXITSTATUS(status)
  1560. else:
  1561. status
  1562. proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
  1563. tags: [ExecIOEffect], noNimScript.} =
  1564. ## Executes a `shell command`:idx:.
  1565. ##
  1566. ## Command has the form 'program args' where args are the command
  1567. ## line arguments given to program. The proc returns the error code
  1568. ## of the shell when it has finished (zero if there is no error).
  1569. ## The proc does not return until the process has finished.
  1570. ##
  1571. ## To execute a program without having a shell involved, use `osproc.execProcess proc
  1572. ## <osproc.html#execProcess,string,string,openArray[string],StringTableRef,set[ProcessOption]>`_.
  1573. ##
  1574. ## **Examples:**
  1575. ##
  1576. ## .. code-block::
  1577. ## discard execShellCmd("ls -la")
  1578. result = exitStatusLikeShell(c_system(command))
  1579. # Templates for filtering directories and files
  1580. when defined(windows) and not weirdTarget:
  1581. template isDir(f: WIN32_FIND_DATA): bool =
  1582. (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32
  1583. template isFile(f: WIN32_FIND_DATA): bool =
  1584. not isDir(f)
  1585. else:
  1586. template isDir(f: string): bool {.dirty.} =
  1587. dirExists(f)
  1588. template isFile(f: string): bool {.dirty.} =
  1589. fileExists(f)
  1590. template defaultWalkFilter(item): bool =
  1591. ## Walk filter used to return true on both
  1592. ## files and directories
  1593. true
  1594. template walkCommon(pattern: string, filter) =
  1595. ## Common code for getting the files and directories with the
  1596. ## specified `pattern`
  1597. when defined(windows):
  1598. var
  1599. f: WIN32_FIND_DATA
  1600. res: int
  1601. res = findFirstFile(pattern, f)
  1602. if res != -1:
  1603. defer: findClose(res)
  1604. let dotPos = searchExtPos(pattern)
  1605. while true:
  1606. if not skipFindData(f) and filter(f):
  1607. # Windows bug/gotcha: 't*.nim' matches 'tfoo.nims' -.- so we check
  1608. # that the file extensions have the same length ...
  1609. let ff = getFilename(f)
  1610. let idx = ff.len - pattern.len + dotPos
  1611. if dotPos < 0 or idx >= ff.len or (idx >= 0 and ff[idx] == '.') or
  1612. (dotPos >= 0 and dotPos+1 < pattern.len and pattern[dotPos+1] == '*'):
  1613. yield splitFile(pattern).dir / extractFilename(ff)
  1614. if findNextFile(res, f) == 0'i32:
  1615. let errCode = getLastError()
  1616. if errCode == ERROR_NO_MORE_FILES: break
  1617. else: raiseOSError(errCode.OSErrorCode)
  1618. else: # here we use glob
  1619. var
  1620. f: Glob
  1621. res: int
  1622. f.gl_offs = 0
  1623. f.gl_pathc = 0
  1624. f.gl_pathv = nil
  1625. res = glob(pattern, 0, nil, addr(f))
  1626. defer: globfree(addr(f))
  1627. if res == 0:
  1628. for i in 0.. f.gl_pathc - 1:
  1629. assert(f.gl_pathv[i] != nil)
  1630. let path = $f.gl_pathv[i]
  1631. if filter(path):
  1632. yield path
  1633. iterator walkPattern*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} =
  1634. ## Iterate over all the files and directories that match the `pattern`.
  1635. ##
  1636. ## On POSIX this uses the `glob`:idx: call.
  1637. ## `pattern` is OS dependent, but at least the `"\*.ext"`
  1638. ## notation is supported.
  1639. ##
  1640. ## See also:
  1641. ## * `walkFiles iterator <#walkFiles.i,string>`_
  1642. ## * `walkDirs iterator <#walkDirs.i,string>`_
  1643. ## * `walkDir iterator <#walkDir.i,string>`_
  1644. ## * `walkDirRec iterator <#walkDirRec.i,string>`_
  1645. walkCommon(pattern, defaultWalkFilter)
  1646. iterator walkFiles*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} =
  1647. ## Iterate over all the files that match the `pattern`.
  1648. ##
  1649. ## On POSIX this uses the `glob`:idx: call.
  1650. ## `pattern` is OS dependent, but at least the `"\*.ext"`
  1651. ## notation is supported.
  1652. ##
  1653. ## See also:
  1654. ## * `walkPattern iterator <#walkPattern.i,string>`_
  1655. ## * `walkDirs iterator <#walkDirs.i,string>`_
  1656. ## * `walkDir iterator <#walkDir.i,string>`_
  1657. ## * `walkDirRec iterator <#walkDirRec.i,string>`_
  1658. walkCommon(pattern, isFile)
  1659. iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript.} =
  1660. ## Iterate over all the directories that match the `pattern`.
  1661. ##
  1662. ## On POSIX this uses the `glob`:idx: call.
  1663. ## `pattern` is OS dependent, but at least the `"\*.ext"`
  1664. ## notation is supported.
  1665. ##
  1666. ## See also:
  1667. ## * `walkPattern iterator <#walkPattern.i,string>`_
  1668. ## * `walkFiles iterator <#walkFiles.i,string>`_
  1669. ## * `walkDir iterator <#walkDir.i,string>`_
  1670. ## * `walkDirRec iterator <#walkDirRec.i,string>`_
  1671. walkCommon(pattern, isDir)
  1672. proc expandFilename*(filename: string): string {.rtl, extern: "nos$1",
  1673. tags: [ReadDirEffect], noNimScript.} =
  1674. ## Returns the full (`absolute`:idx:) path of an existing file `filename`.
  1675. ##
  1676. ## Raises `OSError` in case of an error. Follows symlinks.
  1677. when defined(windows):
  1678. var bufsize = MAX_PATH.int32
  1679. when useWinUnicode:
  1680. var unused: WideCString = nil
  1681. var res = newWideCString("", bufsize)
  1682. while true:
  1683. var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused)
  1684. if L == 0'i32:
  1685. raiseOSError(osLastError())
  1686. elif L > bufsize:
  1687. res = newWideCString("", L)
  1688. bufsize = L
  1689. else:
  1690. result = res$L
  1691. break
  1692. else:
  1693. var unused: cstring = nil
  1694. result = newString(bufsize)
  1695. while true:
  1696. var L = getFullPathNameA(filename, bufsize, result, unused)
  1697. if L == 0'i32:
  1698. raiseOSError(osLastError())
  1699. elif L > bufsize:
  1700. result = newString(L)
  1701. bufsize = L
  1702. else:
  1703. setLen(result, L)
  1704. break
  1705. # getFullPathName doesn't do case corrections, so we have to use this convoluted
  1706. # way of retrieving the true filename
  1707. for x in walkFiles(result):
  1708. result = x
  1709. if not existsFile(result) and not existsDir(result):
  1710. raise newException(OSError, "file '" & result & "' does not exist")
  1711. else:
  1712. # according to Posix we don't need to allocate space for result pathname.
  1713. # But we need to free return value with free(3).
  1714. var r = realpath(filename, nil)
  1715. if r.isNil:
  1716. raiseOSError(osLastError())
  1717. else:
  1718. result = $r
  1719. c_free(cast[pointer](r))
  1720. type
  1721. PathComponent* = enum ## Enumeration specifying a path component.
  1722. ##
  1723. ## See also:
  1724. ## * `walkDirRec iterator <#walkDirRec.i,string>`_
  1725. ## * `FileInfo object <#FileInfo>`_
  1726. pcFile, ## path refers to a file
  1727. pcLinkToFile, ## path refers to a symbolic link to a file
  1728. pcDir, ## path refers to a directory
  1729. pcLinkToDir ## path refers to a symbolic link to a directory
  1730. proc getCurrentCompilerExe*(): string {.compileTime.} = discard
  1731. ## This is `getAppFilename() <#getAppFilename>`_ at compile time.
  1732. ##
  1733. ## Can be used to retrieve the currently executing
  1734. ## Nim compiler from a Nim or nimscript program, or the nimble binary
  1735. ## inside a nimble program (likewise with other binaries built from
  1736. ## compiler API).
  1737. when defined(posix) and not weirdTarget:
  1738. proc getSymlinkFileKind(path: string): PathComponent =
  1739. # Helper function.
  1740. var s: Stat
  1741. assert(path != "")
  1742. if stat(path, s) == 0'i32 and S_ISDIR(s.st_mode):
  1743. result = pcLinkToDir
  1744. else:
  1745. result = pcLinkToFile
  1746. proc staticWalkDir(dir: string; relative: bool): seq[
  1747. tuple[kind: PathComponent, path: string]] =
  1748. discard
  1749. iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: string] {.
  1750. tags: [ReadDirEffect].} =
  1751. ## Walks over the directory `dir` and yields for each directory or file in
  1752. ## `dir`. The component type and full path for each item are returned.
  1753. ##
  1754. ## Walking is not recursive. If ``relative`` is true (default: false)
  1755. ## the resulting path is shortened to be relative to ``dir``.
  1756. ## Example: This directory structure::
  1757. ## dirA / dirB / fileB1.txt
  1758. ## / dirC
  1759. ## / fileA1.txt
  1760. ## / fileA2.txt
  1761. ##
  1762. ## and this code:
  1763. ##
  1764. ## .. code-block:: Nim
  1765. ## for kind, path in walkDir("dirA"):
  1766. ## echo(path)
  1767. ##
  1768. ## produce this output (but not necessarily in this order!)::
  1769. ## dirA/dirB
  1770. ## dirA/dirC
  1771. ## dirA/fileA1.txt
  1772. ## dirA/fileA2.txt
  1773. ##
  1774. ## See also:
  1775. ## * `walkPattern iterator <#walkPattern.i,string>`_
  1776. ## * `walkFiles iterator <#walkFiles.i,string>`_
  1777. ## * `walkDirs iterator <#walkDirs.i,string>`_
  1778. ## * `walkDirRec iterator <#walkDirRec.i,string>`_
  1779. when nimvm:
  1780. for k, v in items(staticWalkDir(dir, relative)):
  1781. yield (k, v)
  1782. else:
  1783. when weirdTarget:
  1784. for k, v in items(staticWalkDir(dir, relative)):
  1785. yield (k, v)
  1786. elif defined(windows):
  1787. var f: WIN32_FIND_DATA
  1788. var h = findFirstFile(dir / "*", f)
  1789. if h != -1:
  1790. defer: findClose(h)
  1791. while true:
  1792. var k = pcFile
  1793. if not skipFindData(f):
  1794. if (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  1795. k = pcDir
  1796. if (f.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  1797. k = succ(k)
  1798. let xx = if relative: extractFilename(getFilename(f))
  1799. else: dir / extractFilename(getFilename(f))
  1800. yield (k, xx)
  1801. if findNextFile(h, f) == 0'i32:
  1802. let errCode = getLastError()
  1803. if errCode == ERROR_NO_MORE_FILES: break
  1804. else: raiseOSError(errCode.OSErrorCode)
  1805. else:
  1806. var d = opendir(dir)
  1807. if d != nil:
  1808. defer: discard closedir(d)
  1809. while true:
  1810. var x = readdir(d)
  1811. if x == nil: break
  1812. when defined(nimNoArrayToCstringConversion):
  1813. var y = $cstring(addr x.d_name)
  1814. else:
  1815. var y = $x.d_name.cstring
  1816. if y != "." and y != "..":
  1817. var s: Stat
  1818. let path = dir / y
  1819. if not relative:
  1820. y = path
  1821. var k = pcFile
  1822. when defined(linux) or defined(macosx) or
  1823. defined(bsd) or defined(genode) or defined(nintendoswitch):
  1824. if x.d_type != DT_UNKNOWN:
  1825. if x.d_type == DT_DIR: k = pcDir
  1826. if x.d_type == DT_LNK:
  1827. if dirExists(path): k = pcLinkToDir
  1828. else: k = pcLinkToFile
  1829. yield (k, y)
  1830. continue
  1831. if lstat(path, s) < 0'i32: break
  1832. if S_ISDIR(s.st_mode):
  1833. k = pcDir
  1834. elif S_ISLNK(s.st_mode):
  1835. k = getSymlinkFileKind(path)
  1836. yield (k, y)
  1837. iterator walkDirRec*(dir: string,
  1838. yieldFilter = {pcFile}, followFilter = {pcDir},
  1839. relative = false): string {.tags: [ReadDirEffect].} =
  1840. ## Recursively walks over the directory `dir` and yields for each file
  1841. ## or directory in `dir`.
  1842. ##
  1843. ## If ``relative`` is true (default: false) the resulting path is
  1844. ## shortened to be relative to ``dir``, otherwise the full path is returned.
  1845. ##
  1846. ## **Warning**:
  1847. ## Modifying the directory structure while the iterator
  1848. ## is traversing may result in undefined behavior!
  1849. ##
  1850. ## Walking is recursive. `followFilter` controls the behaviour of the iterator:
  1851. ##
  1852. ## --------------------- ---------------------------------------------
  1853. ## yieldFilter meaning
  1854. ## --------------------- ---------------------------------------------
  1855. ## ``pcFile`` yield real files (default)
  1856. ## ``pcLinkToFile`` yield symbolic links to files
  1857. ## ``pcDir`` yield real directories
  1858. ## ``pcLinkToDir`` yield symbolic links to directories
  1859. ## --------------------- ---------------------------------------------
  1860. ##
  1861. ## --------------------- ---------------------------------------------
  1862. ## followFilter meaning
  1863. ## --------------------- ---------------------------------------------
  1864. ## ``pcDir`` follow real directories (default)
  1865. ## ``pcLinkToDir`` follow symbolic links to directories
  1866. ## --------------------- ---------------------------------------------
  1867. ##
  1868. ##
  1869. ## See also:
  1870. ## * `walkPattern iterator <#walkPattern.i,string>`_
  1871. ## * `walkFiles iterator <#walkFiles.i,string>`_
  1872. ## * `walkDirs iterator <#walkDirs.i,string>`_
  1873. ## * `walkDir iterator <#walkDir.i,string>`_
  1874. var stack = @[""]
  1875. while stack.len > 0:
  1876. let d = stack.pop()
  1877. for k, p in walkDir(dir / d, relative = true):
  1878. let rel = d / p
  1879. if k in {pcDir, pcLinkToDir} and k in followFilter:
  1880. stack.add rel
  1881. if k in yieldFilter:
  1882. yield if relative: rel else: dir / rel
  1883. proc rawRemoveDir(dir: string) {.noNimScript.} =
  1884. when defined(windows):
  1885. when useWinUnicode:
  1886. wrapUnary(res, removeDirectoryW, dir)
  1887. else:
  1888. var res = removeDirectoryA(dir)
  1889. let lastError = osLastError()
  1890. if res == 0'i32 and lastError.int32 != 3'i32 and
  1891. lastError.int32 != 18'i32 and lastError.int32 != 2'i32:
  1892. raiseOSError(lastError)
  1893. else:
  1894. if rmdir(dir) != 0'i32 and errno != ENOENT: raiseOSError(osLastError())
  1895. proc removeDir*(dir: string) {.rtl, extern: "nos$1", tags: [
  1896. WriteDirEffect, ReadDirEffect], benign, noNimScript.} =
  1897. ## Removes the directory `dir` including all subdirectories and files
  1898. ## in `dir` (recursively).
  1899. ##
  1900. ## If this fails, `OSError` is raised. This does not fail if the directory never
  1901. ## existed in the first place.
  1902. ##
  1903. ## See also:
  1904. ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
  1905. ## * `removeFile proc <#removeFile,string>`_
  1906. ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_
  1907. ## * `createDir proc <#createDir,string>`_
  1908. ## * `copyDir proc <#copyDir,string,string>`_
  1909. ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  1910. ## * `moveDir proc <#moveDir,string,string>`_
  1911. for kind, path in walkDir(dir):
  1912. case kind
  1913. of pcFile, pcLinkToFile, pcLinkToDir: removeFile(path)
  1914. of pcDir: removeDir(path)
  1915. rawRemoveDir(dir)
  1916. proc rawCreateDir(dir: string): bool {.noNimScript.} =
  1917. # Try to create one directory (not the whole path).
  1918. # returns `true` for success, `false` if the path has previously existed
  1919. #
  1920. # This is a thin wrapper over mkDir (or alternatives on other systems),
  1921. # so in case of a pre-existing path we don't check that it is a directory.
  1922. when defined(solaris):
  1923. let res = mkdir(dir, 0o777)
  1924. if res == 0'i32:
  1925. result = true
  1926. elif errno in {EEXIST, ENOSYS}:
  1927. result = false
  1928. else:
  1929. raiseOSError(osLastError(), dir)
  1930. elif defined(haiku):
  1931. let res = mkdir(dir, 0o777)
  1932. if res == 0'i32:
  1933. result = true
  1934. elif errno == EEXIST or errno == EROFS:
  1935. result = false
  1936. else:
  1937. raiseOSError(osLastError(), dir)
  1938. elif defined(posix):
  1939. let res = mkdir(dir, 0o777)
  1940. if res == 0'i32:
  1941. result = true
  1942. elif errno == EEXIST:
  1943. result = false
  1944. else:
  1945. #echo res
  1946. raiseOSError(osLastError(), dir)
  1947. else:
  1948. when useWinUnicode:
  1949. wrapUnary(res, createDirectoryW, dir)
  1950. else:
  1951. let res = createDirectoryA(dir)
  1952. if res != 0'i32:
  1953. result = true
  1954. elif getLastError() == 183'i32:
  1955. result = false
  1956. else:
  1957. raiseOSError(osLastError(), dir)
  1958. proc existsOrCreateDir*(dir: string): bool {.rtl, extern: "nos$1",
  1959. tags: [WriteDirEffect, ReadDirEffect], noNimScript.} =
  1960. ## Check if a `directory`:idx: `dir` exists, and create it otherwise.
  1961. ##
  1962. ## Does not create parent directories (fails if parent does not exist).
  1963. ## Returns `true` if the directory already exists, and `false`
  1964. ## otherwise.
  1965. ##
  1966. ## See also:
  1967. ## * `removeDir proc <#removeDir,string>`_
  1968. ## * `createDir proc <#createDir,string>`_
  1969. ## * `copyDir proc <#copyDir,string,string>`_
  1970. ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  1971. ## * `moveDir proc <#moveDir,string,string>`_
  1972. result = not rawCreateDir(dir)
  1973. if result:
  1974. # path already exists - need to check that it is indeed a directory
  1975. if not existsDir(dir):
  1976. raise newException(IOError, "Failed to create '" & dir & "'")
  1977. proc createDir*(dir: string) {.rtl, extern: "nos$1",
  1978. tags: [WriteDirEffect, ReadDirEffect], noNimScript.} =
  1979. ## Creates the `directory`:idx: `dir`.
  1980. ##
  1981. ## The directory may contain several subdirectories that do not exist yet.
  1982. ## The full path is created. If this fails, `OSError` is raised.
  1983. ##
  1984. ## It does **not** fail if the directory already exists because for
  1985. ## most usages this does not indicate an error.
  1986. ##
  1987. ## See also:
  1988. ## * `removeDir proc <#removeDir,string>`_
  1989. ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_
  1990. ## * `copyDir proc <#copyDir,string,string>`_
  1991. ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  1992. ## * `moveDir proc <#moveDir,string,string>`_
  1993. var omitNext = false
  1994. when doslikeFileSystem:
  1995. omitNext = isAbsolute(dir)
  1996. for i in 1.. dir.len-1:
  1997. if dir[i] in {DirSep, AltSep}:
  1998. if omitNext:
  1999. omitNext = false
  2000. else:
  2001. discard existsOrCreateDir(substr(dir, 0, i-1))
  2002. # The loop does not create the dir itself if it doesn't end in separator
  2003. if dir.len > 0 and not omitNext and
  2004. dir[^1] notin {DirSep, AltSep}:
  2005. discard existsOrCreateDir(dir)
  2006. proc copyDir*(source, dest: string) {.rtl, extern: "nos$1",
  2007. tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} =
  2008. ## Copies a directory from `source` to `dest`.
  2009. ##
  2010. ## If this fails, `OSError` is raised.
  2011. ##
  2012. ## On the Windows platform this proc will copy the attributes from
  2013. ## `source` into `dest`.
  2014. ##
  2015. ## On other platforms created files and directories will inherit the
  2016. ## default permissions of a newly created file/directory for the user.
  2017. ## Use `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  2018. ## to preserve attributes recursively on these platforms.
  2019. ##
  2020. ## See also:
  2021. ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  2022. ## * `copyFile proc <#copyFile,string,string>`_
  2023. ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_
  2024. ## * `removeDir proc <#removeDir,string>`_
  2025. ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_
  2026. ## * `createDir proc <#createDir,string>`_
  2027. ## * `moveDir proc <#moveDir,string,string>`_
  2028. createDir(dest)
  2029. for kind, path in walkDir(source):
  2030. var noSource = splitPath(path).tail
  2031. case kind
  2032. of pcFile:
  2033. copyFile(path, dest / noSource)
  2034. of pcDir:
  2035. copyDir(path, dest / noSource)
  2036. else: discard
  2037. proc moveDir*(source, dest: string) {.tags: [ReadIOEffect, WriteIOEffect], noNimScript.} =
  2038. ## Moves a directory from `source` to `dest`.
  2039. ##
  2040. ## If this fails, `OSError` is raised.
  2041. ##
  2042. ## See also:
  2043. ## * `moveFile proc <#moveFile,string,string>`_
  2044. ## * `copyDir proc <#copyDir,string,string>`_
  2045. ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  2046. ## * `removeDir proc <#removeDir,string>`_
  2047. ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_
  2048. ## * `createDir proc <#createDir,string>`_
  2049. if not tryMoveFSObject(source, dest):
  2050. when not defined(windows):
  2051. # Fallback to copy & del
  2052. copyDir(source, dest)
  2053. removeDir(source)
  2054. proc createSymlink*(src, dest: string) {.noNimScript.} =
  2055. ## Create a symbolic link at `dest` which points to the item specified
  2056. ## by `src`. On most operating systems, will fail if a link already exists.
  2057. ##
  2058. ## **Warning**:
  2059. ## Some OS's (such as Microsoft Windows) restrict the creation
  2060. ## of symlinks to root users (administrators).
  2061. ##
  2062. ## See also:
  2063. ## * `createHardlink proc <#createHardlink,string,string>`_
  2064. ## * `expandSymlink proc <#expandSymlink,string>`_
  2065. when defined(Windows):
  2066. # 2 is the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE. This allows
  2067. # anyone with developer mode on to create a link
  2068. let flag = dirExists(src).int32 or 2
  2069. when useWinUnicode:
  2070. var wSrc = newWideCString(src)
  2071. var wDst = newWideCString(dest)
  2072. if createSymbolicLinkW(wDst, wSrc, flag) == 0 or getLastError() != 0:
  2073. raiseOSError(osLastError())
  2074. else:
  2075. if createSymbolicLinkA(dest, src, flag) == 0 or getLastError() != 0:
  2076. raiseOSError(osLastError())
  2077. else:
  2078. if symlink(src, dest) != 0:
  2079. raiseOSError(osLastError())
  2080. proc createHardlink*(src, dest: string) {.noNimScript.} =
  2081. ## Create a hard link at `dest` which points to the item specified
  2082. ## by `src`.
  2083. ##
  2084. ## **Warning**: Some OS's restrict the creation of hard links to
  2085. ## root users (administrators).
  2086. ##
  2087. ## See also:
  2088. ## * `createSymlink proc <#createSymlink,string,string>`_
  2089. when defined(Windows):
  2090. when useWinUnicode:
  2091. var wSrc = newWideCString(src)
  2092. var wDst = newWideCString(dest)
  2093. if createHardLinkW(wDst, wSrc, nil) == 0:
  2094. raiseOSError(osLastError())
  2095. else:
  2096. if createHardLinkA(dest, src, nil) == 0:
  2097. raiseOSError(osLastError())
  2098. else:
  2099. if link(src, dest) != 0:
  2100. raiseOSError(osLastError())
  2101. proc copyFileWithPermissions*(source, dest: string,
  2102. ignorePermissionErrors = true) {.noNimScript.} =
  2103. ## Copies a file from `source` to `dest` preserving file permissions.
  2104. ##
  2105. ## This is a wrapper proc around `copyFile <#copyFile,string,string>`_,
  2106. ## `getFilePermissions <#getFilePermissions,string>`_ and
  2107. ## `setFilePermissions<#setFilePermissions,string,set[FilePermission]>`_
  2108. ## procs on non-Windows platforms.
  2109. ##
  2110. ## On Windows this proc is just a wrapper for `copyFile proc
  2111. ## <#copyFile,string,string>`_ since that proc already copies attributes.
  2112. ##
  2113. ## On non-Windows systems permissions are copied after the file itself has
  2114. ## been copied, which won't happen atomically and could lead to a race
  2115. ## condition. If `ignorePermissionErrors` is true (default), errors while
  2116. ## reading/setting file attributes will be ignored, otherwise will raise
  2117. ## `OSError`.
  2118. ##
  2119. ## See also:
  2120. ## * `copyFile proc <#copyFile,string,string>`_
  2121. ## * `copyDir proc <#copyDir,string,string>`_
  2122. ## * `tryRemoveFile proc <#tryRemoveFile,string>`_
  2123. ## * `removeFile proc <#removeFile,string>`_
  2124. ## * `moveFile proc <#moveFile,string,string>`_
  2125. ## * `copyDirWithPermissions proc <#copyDirWithPermissions,string,string>`_
  2126. copyFile(source, dest)
  2127. when not defined(Windows):
  2128. try:
  2129. setFilePermissions(dest, getFilePermissions(source))
  2130. except:
  2131. if not ignorePermissionErrors:
  2132. raise
  2133. proc copyDirWithPermissions*(source, dest: string,
  2134. ignorePermissionErrors = true) {.rtl, extern: "nos$1",
  2135. tags: [WriteIOEffect, ReadIOEffect], benign, noNimScript.} =
  2136. ## Copies a directory from `source` to `dest` preserving file permissions.
  2137. ##
  2138. ## If this fails, `OSError` is raised. This is a wrapper proc around `copyDir
  2139. ## <#copyDir,string,string>`_ and `copyFileWithPermissions
  2140. ## <#copyFileWithPermissions,string,string>`_ procs
  2141. ## on non-Windows platforms.
  2142. ##
  2143. ## On Windows this proc is just a wrapper for `copyDir proc
  2144. ## <#copyDir,string,string>`_ since that proc already copies attributes.
  2145. ##
  2146. ## On non-Windows systems permissions are copied after the file or directory
  2147. ## itself has been copied, which won't happen atomically and could lead to a
  2148. ## race condition. If `ignorePermissionErrors` is true (default), errors while
  2149. ## reading/setting file attributes will be ignored, otherwise will raise
  2150. ## `OSError`.
  2151. ##
  2152. ## See also:
  2153. ## * `copyDir proc <#copyDir,string,string>`_
  2154. ## * `copyFile proc <#copyFile,string,string>`_
  2155. ## * `copyFileWithPermissions proc <#copyFileWithPermissions,string,string>`_
  2156. ## * `removeDir proc <#removeDir,string>`_
  2157. ## * `moveDir proc <#moveDir,string,string>`_
  2158. ## * `existsOrCreateDir proc <#existsOrCreateDir,string>`_
  2159. ## * `createDir proc <#createDir,string>`_
  2160. createDir(dest)
  2161. when not defined(Windows):
  2162. try:
  2163. setFilePermissions(dest, getFilePermissions(source))
  2164. except:
  2165. if not ignorePermissionErrors:
  2166. raise
  2167. for kind, path in walkDir(source):
  2168. var noSource = splitPath(path).tail
  2169. case kind
  2170. of pcFile:
  2171. copyFileWithPermissions(path, dest / noSource, ignorePermissionErrors)
  2172. of pcDir:
  2173. copyDirWithPermissions(path, dest / noSource, ignorePermissionErrors)
  2174. else: discard
  2175. proc inclFilePermissions*(filename: string,
  2176. permissions: set[FilePermission]) {.
  2177. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} =
  2178. ## A convenience proc for:
  2179. ##
  2180. ## .. code-block:: nim
  2181. ## setFilePermissions(filename, getFilePermissions(filename)+permissions)
  2182. setFilePermissions(filename, getFilePermissions(filename)+permissions)
  2183. proc exclFilePermissions*(filename: string,
  2184. permissions: set[FilePermission]) {.
  2185. rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noNimScript.} =
  2186. ## A convenience proc for:
  2187. ##
  2188. ## .. code-block:: nim
  2189. ## setFilePermissions(filename, getFilePermissions(filename)-permissions)
  2190. setFilePermissions(filename, getFilePermissions(filename)-permissions)
  2191. proc expandSymlink*(symlinkPath: string): string {.noNimScript.} =
  2192. ## Returns a string representing the path to which the symbolic link points.
  2193. ##
  2194. ## On Windows this is a noop, ``symlinkPath`` is simply returned.
  2195. ##
  2196. ## See also:
  2197. ## * `createSymlink proc <#createSymlink,string,string>`_
  2198. when defined(windows):
  2199. result = symlinkPath
  2200. else:
  2201. result = newString(256)
  2202. var len = readlink(symlinkPath, result, 256)
  2203. if len < 0:
  2204. raiseOSError(osLastError())
  2205. if len > 256:
  2206. result = newString(len+1)
  2207. len = readlink(symlinkPath, result, len)
  2208. setLen(result, len)
  2209. proc parseCmdLine*(c: string): seq[string] {.
  2210. noSideEffect, rtl, extern: "nos$1".} =
  2211. ## Splits a `command line`:idx: into several components.
  2212. ##
  2213. ## **Note**: This proc is only occasionally useful, better use the
  2214. ## `parseopt module <parseopt.html>`_.
  2215. ##
  2216. ## On Windows, it uses the `following parsing rules
  2217. ## <http://msdn.microsoft.com/en-us/library/17w5ykft.aspx>`_:
  2218. ##
  2219. ## * Arguments are delimited by white space, which is either a space or a tab.
  2220. ## * The caret character (^) is not recognized as an escape character or
  2221. ## delimiter. The character is handled completely by the command-line parser
  2222. ## in the operating system before being passed to the argv array in the
  2223. ## program.
  2224. ## * A string surrounded by double quotation marks ("string") is interpreted
  2225. ## as a single argument, regardless of white space contained within. A
  2226. ## quoted string can be embedded in an argument.
  2227. ## * A double quotation mark preceded by a backslash (\") is interpreted as a
  2228. ## literal double quotation mark character (").
  2229. ## * Backslashes are interpreted literally, unless they immediately precede
  2230. ## a double quotation mark.
  2231. ## * If an even number of backslashes is followed by a double quotation mark,
  2232. ## one backslash is placed in the argv array for every pair of backslashes,
  2233. ## and the double quotation mark is interpreted as a string delimiter.
  2234. ## * If an odd number of backslashes is followed by a double quotation mark,
  2235. ## one backslash is placed in the argv array for every pair of backslashes,
  2236. ## and the double quotation mark is "escaped" by the remaining backslash,
  2237. ## causing a literal double quotation mark (") to be placed in argv.
  2238. ##
  2239. ## On Posix systems, it uses the following parsing rules:
  2240. ## Components are separated by whitespace unless the whitespace
  2241. ## occurs within ``"`` or ``'`` quotes.
  2242. ##
  2243. ## See also:
  2244. ## * `parseopt module <parseopt.html>`_
  2245. ## * `paramCount proc <#paramCount>`_
  2246. ## * `paramStr proc <#paramStr,int>`_
  2247. ## * `commandLineParams proc <#commandLineParams>`_
  2248. result = @[]
  2249. var i = 0
  2250. var a = ""
  2251. while true:
  2252. setLen(a, 0)
  2253. # eat all delimiting whitespace
  2254. while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i)
  2255. if i >= c.len: break
  2256. when defined(windows):
  2257. # parse a single argument according to the above rules:
  2258. var inQuote = false
  2259. while i < c.len:
  2260. case c[i]
  2261. of '\\':
  2262. var j = i
  2263. while j < c.len and c[j] == '\\': inc(j)
  2264. if j < c.len and c[j] == '"':
  2265. for k in 1..(j-i) div 2: a.add('\\')
  2266. if (j-i) mod 2 == 0:
  2267. i = j
  2268. else:
  2269. a.add('"')
  2270. i = j+1
  2271. else:
  2272. a.add(c[i])
  2273. inc(i)
  2274. of '"':
  2275. inc(i)
  2276. if not inQuote: inQuote = true
  2277. elif i < c.len and c[i] == '"':
  2278. a.add(c[i])
  2279. inc(i)
  2280. else:
  2281. inQuote = false
  2282. break
  2283. of ' ', '\t':
  2284. if not inQuote: break
  2285. a.add(c[i])
  2286. inc(i)
  2287. else:
  2288. a.add(c[i])
  2289. inc(i)
  2290. else:
  2291. case c[i]
  2292. of '\'', '\"':
  2293. var delim = c[i]
  2294. inc(i) # skip ' or "
  2295. while i < c.len and c[i] != delim:
  2296. add a, c[i]
  2297. inc(i)
  2298. if i < c.len: inc(i)
  2299. else:
  2300. while i < c.len and c[i] > ' ':
  2301. add(a, c[i])
  2302. inc(i)
  2303. add(result, a)
  2304. when defined(nimdoc):
  2305. # Common forward declaration docstring block for parameter retrieval procs.
  2306. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  2307. ## Returns the number of `command line arguments`:idx: given to the
  2308. ## application.
  2309. ##
  2310. ## Unlike `argc`:idx: in C, if your binary was called without parameters this
  2311. ## will return zero.
  2312. ## You can query each individual parameter with `paramStr proc <#paramStr,int>`_
  2313. ## or retrieve all of them in one go with `commandLineParams proc
  2314. ## <#commandLineParams>`_.
  2315. ##
  2316. ## **Availability**: When generating a dynamic library (see `--app:lib`) on
  2317. ## Posix this proc is not defined.
  2318. ## Test for availability using `declared() <system.html#declared,untyped>`_.
  2319. ##
  2320. ## See also:
  2321. ## * `parseopt module <parseopt.html>`_
  2322. ## * `parseCmdLine proc <#parseCmdLine,string>`_
  2323. ## * `paramStr proc <#paramStr,int>`_
  2324. ## * `commandLineParams proc <#commandLineParams>`_
  2325. ##
  2326. ## **Examples:**
  2327. ##
  2328. ## .. code-block:: nim
  2329. ## when declared(paramCount):
  2330. ## # Use paramCount() here
  2331. ## else:
  2332. ## # Do something else!
  2333. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  2334. ## Returns the `i`-th `command line argument`:idx: given to the application.
  2335. ##
  2336. ## `i` should be in the range `1..paramCount()`, the `IndexError`
  2337. ## exception will be raised for invalid values. Instead of iterating over
  2338. ## `paramCount() <#paramCount>`_ with this proc you can call the
  2339. ## convenience `commandLineParams() <#commandLineParams>`_.
  2340. ##
  2341. ## Similarly to `argv`:idx: in C,
  2342. ## it is possible to call ``paramStr(0)`` but this will return OS specific
  2343. ## contents (usually the name of the invoked executable). You should avoid
  2344. ## this and call `getAppFilename() <#getAppFilename>`_ instead.
  2345. ##
  2346. ## **Availability**: When generating a dynamic library (see `--app:lib`) on
  2347. ## Posix this proc is not defined.
  2348. ## Test for availability using `declared() <system.html#declared,untyped>`_.
  2349. ##
  2350. ## See also:
  2351. ## * `parseopt module <parseopt.html>`_
  2352. ## * `parseCmdLine proc <#parseCmdLine,string>`_
  2353. ## * `paramCount proc <#paramCount>`_
  2354. ## * `commandLineParams proc <#commandLineParams>`_
  2355. ## * `getAppFilename proc <#getAppFilename>`_
  2356. ##
  2357. ## **Examples:**
  2358. ##
  2359. ## .. code-block:: nim
  2360. ## when declared(paramStr):
  2361. ## # Use paramStr() here
  2362. ## else:
  2363. ## # Do something else!
  2364. elif defined(nintendoswitch) or weirdTarget:
  2365. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  2366. raise newException(OSError, "paramStr is not implemented on Nintendo Switch")
  2367. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  2368. raise newException(OSError, "paramCount is not implemented on Nintendo Switch")
  2369. elif defined(windows):
  2370. # Since we support GUI applications with Nim, we sometimes generate
  2371. # a WinMain entry proc. But a WinMain proc has no access to the parsed
  2372. # command line arguments. The way to get them differs. Thus we parse them
  2373. # ourselves. This has the additional benefit that the program's behaviour
  2374. # is always the same -- independent of the used C compiler.
  2375. var
  2376. ownArgv {.threadvar.}: seq[string]
  2377. ownParsedArgv {.threadvar.}: bool
  2378. proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
  2379. # Docstring in nimdoc block.
  2380. if not ownParsedArgv:
  2381. ownArgv = parseCmdLine($getCommandLine())
  2382. ownParsedArgv = true
  2383. result = ownArgv.len-1
  2384. proc paramStr*(i: int): TaintedString {.rtl, extern: "nos$1",
  2385. tags: [ReadIOEffect].} =
  2386. # Docstring in nimdoc block.
  2387. if not ownParsedArgv:
  2388. ownArgv = parseCmdLine($getCommandLine())
  2389. ownParsedArgv = true
  2390. if i < ownArgv.len and i >= 0: return TaintedString(ownArgv[i])
  2391. raise newException(IndexError, formatErrorIndexBound(i, ownArgv.len-1))
  2392. elif defined(genode):
  2393. proc paramStr*(i: int): TaintedString =
  2394. raise newException(OSError, "paramStr is not implemented on Genode")
  2395. proc paramCount*(): int =
  2396. raise newException(OSError, "paramCount is not implemented on Genode")
  2397. elif not defined(createNimRtl) and
  2398. not(defined(posix) and appType == "lib"):
  2399. # On Posix, there is no portable way to get the command line from a DLL.
  2400. var
  2401. cmdCount {.importc: "cmdCount".}: cint
  2402. cmdLine {.importc: "cmdLine".}: cstringArray
  2403. proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} =
  2404. # Docstring in nimdoc block.
  2405. if i < cmdCount and i >= 0: return TaintedString($cmdLine[i])
  2406. raise newException(IndexError, formatErrorIndexBound(i, cmdCount-1))
  2407. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  2408. # Docstring in nimdoc block.
  2409. result = cmdCount-1
  2410. when declared(paramCount) or defined(nimdoc):
  2411. proc commandLineParams*(): seq[TaintedString] =
  2412. ## Convenience proc which returns the command line parameters.
  2413. ##
  2414. ## This returns **only** the parameters. If you want to get the application
  2415. ## executable filename, call `getAppFilename() <#getAppFilename>`_.
  2416. ##
  2417. ## **Availability**: On Posix there is no portable way to get the command
  2418. ## line from a DLL and thus the proc isn't defined in this environment. You
  2419. ## can test for its availability with `declared()
  2420. ## <system.html#declared,untyped>`_.
  2421. ##
  2422. ## See also:
  2423. ## * `parseopt module <parseopt.html>`_
  2424. ## * `parseCmdLine proc <#parseCmdLine,string>`_
  2425. ## * `paramCount proc <#paramCount>`_
  2426. ## * `paramStr proc <#paramStr,int>`_
  2427. ## * `getAppFilename proc <#getAppFilename>`_
  2428. ##
  2429. ## **Examples:**
  2430. ##
  2431. ## .. code-block:: nim
  2432. ## when declared(commandLineParams):
  2433. ## # Use commandLineParams() here
  2434. ## else:
  2435. ## # Do something else!
  2436. result = @[]
  2437. for i in 1..paramCount():
  2438. result.add(paramStr(i))
  2439. when not weirdTarget and (defined(freebsd) or defined(dragonfly)):
  2440. proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize,
  2441. newp: pointer, newplen: csize): cint
  2442. {.importc: "sysctl",header: """#include <sys/types.h>
  2443. #include <sys/sysctl.h>"""}
  2444. const
  2445. CTL_KERN = 1
  2446. KERN_PROC = 14
  2447. MAX_PATH = 1024
  2448. when defined(freebsd):
  2449. const KERN_PROC_PATHNAME = 12
  2450. else:
  2451. const KERN_PROC_PATHNAME = 9
  2452. proc getApplFreebsd(): string =
  2453. var pathLength = csize(MAX_PATH)
  2454. result = newString(pathLength)
  2455. var req = [CTL_KERN.cint, KERN_PROC.cint, KERN_PROC_PATHNAME.cint, -1.cint]
  2456. while true:
  2457. let res = sysctl(addr req[0], 4, cast[pointer](addr result[0]),
  2458. addr pathLength, nil, 0)
  2459. if res < 0:
  2460. let err = osLastError()
  2461. if err.int32 == ENOMEM:
  2462. result = newString(pathLength)
  2463. else:
  2464. result.setLen(0) # error!
  2465. break
  2466. else:
  2467. result.setLen(pathLength)
  2468. break
  2469. when not weirdTarget and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)):
  2470. proc getApplAux(procPath: string): string =
  2471. result = newString(256)
  2472. var len = readlink(procPath, result, 256)
  2473. if len > 256:
  2474. result = newString(len+1)
  2475. len = readlink(procPath, result, len)
  2476. setLen(result, len)
  2477. when not (defined(windows) or defined(macosx) or weirdTarget):
  2478. proc getApplHeuristic(): string =
  2479. when declared(paramStr):
  2480. result = string(paramStr(0))
  2481. # POSIX guaranties that this contains the executable
  2482. # as it has been executed by the calling process
  2483. if len(result) > 0 and result[0] != DirSep: # not an absolute path?
  2484. # iterate over any path in the $PATH environment variable
  2485. for p in split(string(getEnv("PATH")), {PathSep}):
  2486. var x = joinPath(p, result)
  2487. if existsFile(x): return x
  2488. else:
  2489. result = ""
  2490. when defined(macosx):
  2491. type
  2492. cuint32* {.importc: "unsigned int", nodecl.} = int
  2493. ## This is the same as the type ``uint32_t`` in *C*.
  2494. # a really hacky solution: since we like to include 2 headers we have to
  2495. # define two procs which in reality are the same
  2496. proc getExecPath1(c: cstring, size: var cuint32) {.
  2497. importc: "_NSGetExecutablePath", header: "<sys/param.h>".}
  2498. proc getExecPath2(c: cstring, size: var cuint32): bool {.
  2499. importc: "_NSGetExecutablePath", header: "<mach-o/dyld.h>".}
  2500. when defined(haiku):
  2501. const
  2502. PATH_MAX = 1024
  2503. B_FIND_PATH_IMAGE_PATH = 1000
  2504. proc find_path(codePointer: pointer, baseDirectory: cint, subPath: cstring,
  2505. pathBuffer: cstring, bufferSize: csize): int32
  2506. {.importc, header: "<FindDirectory.h>".}
  2507. proc getApplHaiku(): string =
  2508. result = newString(PATH_MAX)
  2509. if find_path(nil, B_FIND_PATH_IMAGE_PATH, nil, result, PATH_MAX) == 0:
  2510. let realLen = len(cstring(result))
  2511. setLen(result, realLen)
  2512. else:
  2513. result = ""
  2514. proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} =
  2515. ## Returns the filename of the application's executable.
  2516. ## This proc will resolve symlinks.
  2517. ##
  2518. ## See also:
  2519. ## * `getAppDir proc <#getAppDir>`_
  2520. ## * `getCurrentCompilerExe proc <#getCurrentCompilerExe>`_
  2521. # Linux: /proc/<pid>/exe
  2522. # Solaris:
  2523. # /proc/<pid>/object/a.out (filename only)
  2524. # /proc/<pid>/path/a.out (complete pathname)
  2525. when defined(windows):
  2526. var bufsize = int32(MAX_PATH)
  2527. when useWinUnicode:
  2528. var buf = newWideCString("", bufsize)
  2529. while true:
  2530. var L = getModuleFileNameW(0, buf, bufsize)
  2531. if L == 0'i32:
  2532. result = "" # error!
  2533. break
  2534. elif L > bufsize:
  2535. buf = newWideCString("", L)
  2536. bufsize = L
  2537. else:
  2538. result = buf$L
  2539. break
  2540. else:
  2541. result = newString(bufsize)
  2542. while true:
  2543. var L = getModuleFileNameA(0, result, bufsize)
  2544. if L == 0'i32:
  2545. result = "" # error!
  2546. break
  2547. elif L > bufsize:
  2548. result = newString(L)
  2549. bufsize = L
  2550. else:
  2551. setLen(result, L)
  2552. break
  2553. elif defined(macosx):
  2554. var size: cuint32
  2555. getExecPath1(nil, size)
  2556. result = newString(int(size))
  2557. if getExecPath2(result, size):
  2558. result = "" # error!
  2559. if result.len > 0:
  2560. result = result.expandFilename
  2561. else:
  2562. when defined(linux) or defined(aix) or defined(netbsd):
  2563. result = getApplAux("/proc/self/exe")
  2564. elif defined(solaris):
  2565. result = getApplAux("/proc/" & $getpid() & "/path/a.out")
  2566. elif defined(genode) or defined(nintendoswitch):
  2567. raiseOSError(OSErrorCode(-1), "POSIX command line not supported")
  2568. elif defined(freebsd) or defined(dragonfly):
  2569. result = getApplFreebsd()
  2570. elif defined(haiku):
  2571. result = getApplHaiku()
  2572. # little heuristic that may work on other POSIX-like systems:
  2573. if result.len == 0:
  2574. result = getApplHeuristic()
  2575. proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} =
  2576. ## Returns the directory of the application's executable.
  2577. ##
  2578. ## See also:
  2579. ## * `getAppFilename proc <#getAppFilename>`_
  2580. result = splitFile(getAppFilename()).dir
  2581. proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noNimScript.} =
  2582. ## Sleeps `milsecs` milliseconds.
  2583. when defined(windows):
  2584. winlean.sleep(int32(milsecs))
  2585. else:
  2586. var a, b: Timespec
  2587. a.tv_sec = posix.Time(milsecs div 1000)
  2588. a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
  2589. discard posix.nanosleep(a, b)
  2590. proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1",
  2591. tags: [ReadIOEffect], noNimScript.} =
  2592. ## Returns the file size of `file` (in bytes). ``OSError`` is
  2593. ## raised in case of an error.
  2594. when defined(windows):
  2595. var a: WIN32_FIND_DATA
  2596. var resA = findFirstFile(file, a)
  2597. if resA == -1: raiseOSError(osLastError())
  2598. result = rdFileSize(a)
  2599. findClose(resA)
  2600. else:
  2601. var f: File
  2602. if open(f, file):
  2603. result = getFileSize(f)
  2604. close(f)
  2605. else: raiseOSError(osLastError())
  2606. when defined(Windows) or weirdTarget:
  2607. type
  2608. DeviceId* = int32
  2609. FileId* = int64
  2610. else:
  2611. type
  2612. DeviceId* = Dev
  2613. FileId* = Ino
  2614. type
  2615. FileInfo* = object
  2616. ## Contains information associated with a file object.
  2617. ##
  2618. ## See also:
  2619. ## * `getFileInfo(handle) proc <#getFileInfo,FileHandle>`_
  2620. ## * `getFileInfo(file) proc <#getFileInfo,File>`_
  2621. ## * `getFileInfo(path) proc <#getFileInfo,string>`_
  2622. id*: tuple[device: DeviceId, file: FileId] ## Device and file id.
  2623. kind*: PathComponent ## Kind of file object - directory, symlink, etc.
  2624. size*: BiggestInt ## Size of file.
  2625. permissions*: set[FilePermission] ## File permissions
  2626. linkCount*: BiggestInt ## Number of hard links the file object has.
  2627. lastAccessTime*: times.Time ## Time file was last accessed.
  2628. lastWriteTime*: times.Time ## Time file was last modified/written to.
  2629. creationTime*: times.Time ## Time file was created. Not supported on all systems!
  2630. template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped =
  2631. ## Transforms the native file info structure into the one nim uses.
  2632. ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows,
  2633. ## or a 'Stat' structure on posix
  2634. when defined(Windows):
  2635. template merge(a, b): untyped = a or (b shl 32)
  2636. formalInfo.id.device = rawInfo.dwVolumeSerialNumber
  2637. formalInfo.id.file = merge(rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh)
  2638. formalInfo.size = merge(rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh)
  2639. formalInfo.linkCount = rawInfo.nNumberOfLinks
  2640. formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime))
  2641. formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime))
  2642. formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime))
  2643. # Retrieve basic permissions
  2644. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32:
  2645. formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec,
  2646. fpGroupRead, fpOthersExec, fpOthersRead}
  2647. else:
  2648. result.permissions = {fpUserExec..fpOthersRead}
  2649. # Retrieve basic file kind
  2650. result.kind = pcFile
  2651. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32:
  2652. formalInfo.kind = pcDir
  2653. if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32:
  2654. formalInfo.kind = succ(result.kind)
  2655. else:
  2656. template checkAndIncludeMode(rawMode, formalMode: untyped) =
  2657. if (rawInfo.st_mode and rawMode.Mode) != 0.Mode:
  2658. formalInfo.permissions.incl(formalMode)
  2659. formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino)
  2660. formalInfo.size = rawInfo.st_size
  2661. formalInfo.linkCount = rawInfo.st_nlink.BiggestInt
  2662. formalInfo.lastAccessTime = rawInfo.st_atim.toTime
  2663. formalInfo.lastWriteTime = rawInfo.st_mtim.toTime
  2664. formalInfo.creationTime = rawInfo.st_ctim.toTime
  2665. result.permissions = {}
  2666. checkAndIncludeMode(S_IRUSR, fpUserRead)
  2667. checkAndIncludeMode(S_IWUSR, fpUserWrite)
  2668. checkAndIncludeMode(S_IXUSR, fpUserExec)
  2669. checkAndIncludeMode(S_IRGRP, fpGroupRead)
  2670. checkAndIncludeMode(S_IWGRP, fpGroupWrite)
  2671. checkAndIncludeMode(S_IXGRP, fpGroupExec)
  2672. checkAndIncludeMode(S_IROTH, fpOthersRead)
  2673. checkAndIncludeMode(S_IWOTH, fpOthersWrite)
  2674. checkAndIncludeMode(S_IXOTH, fpOthersExec)
  2675. formalInfo.kind = pcFile
  2676. if S_ISDIR(rawInfo.st_mode):
  2677. formalInfo.kind = pcDir
  2678. elif S_ISLNK(rawInfo.st_mode):
  2679. assert(path != "") # symlinks can't occur for file handles
  2680. formalInfo.kind = getSymlinkFileKind(path)
  2681. when defined(js):
  2682. when not declared(FileHandle):
  2683. type FileHandle = distinct int32
  2684. when not declared(File):
  2685. type File = object
  2686. proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} =
  2687. ## Retrieves file information for the file object represented by the given
  2688. ## handle.
  2689. ##
  2690. ## If the information cannot be retrieved, such as when the file handle
  2691. ## is invalid, `OSError` is raised.
  2692. ##
  2693. ## See also:
  2694. ## * `getFileInfo(file) proc <#getFileInfo,File>`_
  2695. ## * `getFileInfo(path) proc <#getFileInfo,string>`_
  2696. # Done: ID, Kind, Size, Permissions, Link Count
  2697. when defined(Windows):
  2698. var rawInfo: BY_HANDLE_FILE_INFORMATION
  2699. # We have to use the super special '_get_osfhandle' call (wrapped above)
  2700. # To transform the C file descriptor to a native file handle.
  2701. var realHandle = get_osfhandle(handle)
  2702. if getFileInformationByHandle(realHandle, addr rawInfo) == 0:
  2703. raiseOSError(osLastError())
  2704. rawToFormalFileInfo(rawInfo, "", result)
  2705. else:
  2706. var rawInfo: Stat
  2707. if fstat(handle, rawInfo) < 0'i32:
  2708. raiseOSError(osLastError())
  2709. rawToFormalFileInfo(rawInfo, "", result)
  2710. proc getFileInfo*(file: File): FileInfo {.noNimScript.} =
  2711. ## Retrieves file information for the file object.
  2712. ##
  2713. ## See also:
  2714. ## * `getFileInfo(handle) proc <#getFileInfo,FileHandle>`_
  2715. ## * `getFileInfo(path) proc <#getFileInfo,string>`_
  2716. if file.isNil:
  2717. raise newException(IOError, "File is nil")
  2718. result = getFileInfo(file.getFileHandle())
  2719. proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noNimScript.} =
  2720. ## Retrieves file information for the file object pointed to by `path`.
  2721. ##
  2722. ## Due to intrinsic differences between operating systems, the information
  2723. ## contained by the returned `FileInfo object <#FileInfo>`_ will be slightly
  2724. ## different across platforms, and in some cases, incomplete or inaccurate.
  2725. ##
  2726. ## When `followSymlink` is true (default), symlinks are followed and the
  2727. ## information retrieved is information related to the symlink's target.
  2728. ## Otherwise, information on the symlink itself is retrieved.
  2729. ##
  2730. ## If the information cannot be retrieved, such as when the path doesn't
  2731. ## exist, or when permission restrictions prevent the program from retrieving
  2732. ## file information, `OSError` is raised.
  2733. ##
  2734. ## See also:
  2735. ## * `getFileInfo(handle) proc <#getFileInfo,FileHandle>`_
  2736. ## * `getFileInfo(file) proc <#getFileInfo,File>`_
  2737. when defined(Windows):
  2738. var
  2739. handle = openHandle(path, followSymlink)
  2740. rawInfo: BY_HANDLE_FILE_INFORMATION
  2741. if handle == INVALID_HANDLE_VALUE:
  2742. raiseOSError(osLastError())
  2743. if getFileInformationByHandle(handle, addr rawInfo) == 0:
  2744. raiseOSError(osLastError())
  2745. rawToFormalFileInfo(rawInfo, path, result)
  2746. discard closeHandle(handle)
  2747. else:
  2748. var rawInfo: Stat
  2749. if followSymlink:
  2750. if stat(path, rawInfo) < 0'i32:
  2751. raiseOSError(osLastError())
  2752. else:
  2753. if lstat(path, rawInfo) < 0'i32:
  2754. raiseOSError(osLastError())
  2755. rawToFormalFileInfo(rawInfo, path, result)
  2756. proc isHidden*(path: string): bool {.noNimScript.} =
  2757. ## Determines whether ``path`` is hidden or not, using `this
  2758. ## reference <https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory>`_.
  2759. ##
  2760. ## On Windows: returns true if it exists and its "hidden" attribute is set.
  2761. ##
  2762. ## On posix: returns true if ``lastPathPart(path)`` starts with ``.`` and is
  2763. ## not ``.`` or ``..``.
  2764. ##
  2765. ## **Note**: paths are not normalized to determine `isHidden`.
  2766. runnableExamples:
  2767. when defined(posix):
  2768. assert ".foo".isHidden
  2769. assert not ".foo/bar".isHidden
  2770. assert not ".".isHidden
  2771. assert not "..".isHidden
  2772. assert not "".isHidden
  2773. assert ".foo/".isHidden
  2774. when defined(Windows):
  2775. when useWinUnicode:
  2776. wrapUnary(attributes, getFileAttributesW, path)
  2777. else:
  2778. var attributes = getFileAttributesA(path)
  2779. if attributes != -1'i32:
  2780. result = (attributes and FILE_ATTRIBUTE_HIDDEN) != 0'i32
  2781. else:
  2782. let fileName = lastPathPart(path)
  2783. result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".."
  2784. proc getCurrentProcessId*(): int {.noNimScript.} =
  2785. ## Return current process ID.
  2786. ##
  2787. ## See also:
  2788. ## * `osproc.processID(p: Process) <osproc.html#processID,Process>`_
  2789. when defined(windows):
  2790. proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32",
  2791. importc: "GetCurrentProcessId".}
  2792. result = GetCurrentProcessId().int
  2793. else:
  2794. result = getpid()
  2795. {.pop.}
  2796. proc setLastModificationTime*(file: string, t: times.Time) {.noNimScript.} =
  2797. ## Sets the `file`'s last modification time. `OSError` is raised in case of
  2798. ## an error.
  2799. when defined(posix):
  2800. let unixt = posix.Time(t.toUnix)
  2801. let micro = convert(Nanoseconds, Microseconds, t.nanosecond)
  2802. var timevals = [Timeval(tv_sec: unixt, tv_usec: micro),
  2803. Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification]
  2804. if utimes(file, timevals.addr) != 0: raiseOSError(osLastError())
  2805. else:
  2806. let h = openHandle(path = file, writeAccess = true)
  2807. if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError())
  2808. var ft = t.toWinTime.toFILETIME
  2809. let res = setFileTime(h, nil, nil, ft.addr)
  2810. discard h.closeHandle
  2811. if res == 0'i32: raiseOSError(osLastError())
  2812. when isMainModule:
  2813. assert quoteShellWindows("aaa") == "aaa"
  2814. assert quoteShellWindows("aaa\"") == "aaa\\\""
  2815. assert quoteShellWindows("") == "\"\""
  2816. assert quoteShellPosix("aaa") == "aaa"
  2817. assert quoteShellPosix("aaa a") == "'aaa a'"
  2818. assert quoteShellPosix("") == "''"
  2819. assert quoteShellPosix("a'a") == "'a'\"'\"'a'"
  2820. when defined(posix):
  2821. assert quoteShell("") == "''"
  2822. block normalizePathEndTest:
  2823. # handle edge cases correctly: shouldn't affect whether path is
  2824. # absolute/relative
  2825. doAssert "".normalizePathEnd(true) == ""
  2826. doAssert "".normalizePathEnd(false) == ""
  2827. doAssert "/".normalizePathEnd(true) == $DirSep
  2828. doAssert "/".normalizePathEnd(false) == $DirSep
  2829. when defined(posix):
  2830. doAssert "//".normalizePathEnd(false) == "/"
  2831. doAssert "foo.bar//".normalizePathEnd == "foo.bar"
  2832. doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/"
  2833. when defined(Windows):
  2834. doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo"
  2835. doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\"
  2836. # this one is controversial: we could argue for returning `D:\` instead,
  2837. # but this is simplest.
  2838. doAssert r"D:\".normalizePathEnd == r"D:"
  2839. doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\"
  2840. doAssert "/".normalizePathEnd == r"\"