os.nim 118 KB

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