os.nim 111 KB

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