os.nim 127 KB

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