nimblecmd.nim 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Implements some helper procs for Nimble (Nim's package manager) support.
  10. import parseutils, strutils, strtabs, os, options, msgs, sequtils,
  11. lineinfos, pathutils
  12. proc addPath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) =
  13. if not conf.searchPaths.contains(path):
  14. conf.searchPaths.insert(path, 0)
  15. type
  16. Version* = distinct string
  17. proc `$`*(ver: Version): string {.borrow.}
  18. proc newVersion*(ver: string): Version =
  19. doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits,
  20. "Wrong version: " & ver)
  21. return Version(ver)
  22. proc isSpecial(ver: Version): bool =
  23. return ($ver).len > 0 and ($ver)[0] == '#'
  24. proc isValidVersion(v: string): bool =
  25. if v.len > 0:
  26. if v[0] in {'#'} + Digits: return true
  27. proc `<`*(ver: Version, ver2: Version): bool =
  28. ## This is synced from Nimble's version module.
  29. # Handling for special versions such as "#head" or "#branch".
  30. if ver.isSpecial or ver2.isSpecial:
  31. if ver2.isSpecial and ($ver2).normalize == "#head":
  32. return ($ver).normalize != "#head"
  33. if not ver2.isSpecial:
  34. # `#aa111 < 1.1`
  35. return ($ver).normalize != "#head"
  36. # Handling for normal versions such as "0.1.0" or "1.0".
  37. var sVer = string(ver).split('.')
  38. var sVer2 = string(ver2).split('.')
  39. for i in 0..<max(sVer.len, sVer2.len):
  40. var sVerI = 0
  41. if i < sVer.len:
  42. discard parseInt(sVer[i], sVerI)
  43. var sVerI2 = 0
  44. if i < sVer2.len:
  45. discard parseInt(sVer2[i], sVerI2)
  46. if sVerI < sVerI2:
  47. return true
  48. elif sVerI == sVerI2:
  49. discard
  50. else:
  51. return false
  52. proc getPathVersion*(p: string): tuple[name, version: string] =
  53. ## Splits path ``p`` in the format ``/home/user/.nimble/pkgs/package-0.1``
  54. ## into ``(/home/user/.nimble/pkgs/package, 0.1)``
  55. result.name = ""
  56. result.version = ""
  57. const specialSeparator = "-#"
  58. let last = p.rfind(p.lastPathPart) # the index where the last path part begins
  59. var sepIdx = p.find(specialSeparator, start = last)
  60. if sepIdx == -1:
  61. sepIdx = p.rfind('-', start = last)
  62. if sepIdx == -1:
  63. result.name = p
  64. return
  65. for i in sepIdx..<p.len:
  66. if p[i] in {DirSep, AltSep}:
  67. result.name = p
  68. return
  69. result.name = p[0..sepIdx - 1]
  70. result.version = p.substr(sepIdx + 1)
  71. proc addPackage(conf: ConfigRef; packages: StringTableRef, p: string; info: TLineInfo) =
  72. let (name, ver) = getPathVersion(p)
  73. if isValidVersion(ver):
  74. let version = newVersion(ver)
  75. if packages.getOrDefault(name).newVersion < version or
  76. (not packages.hasKey(name)):
  77. packages[name] = $version
  78. else:
  79. localError(conf, info, "invalid package name: " & p)
  80. iterator chosen(packages: StringTableRef): string =
  81. for key, val in pairs(packages):
  82. let res = if val.len == 0: key else: key & '-' & val
  83. yield res
  84. proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) =
  85. var path = p
  86. let nimbleLinks = toSeq(walkPattern(p / "*.nimble-link"))
  87. if nimbleLinks.len > 0:
  88. # If the user has more than one .nimble-link file then... we just ignore it.
  89. # Spec for these files is available in Nimble's readme:
  90. # https://github.com/nim-lang/nimble#nimble-link
  91. let nimbleLinkLines = readFile(nimbleLinks[0]).splitLines()
  92. path = nimbleLinkLines[1]
  93. if not path.isAbsolute():
  94. path = p / path
  95. if not contains(conf.searchPaths, AbsoluteDir path):
  96. message(conf, info, hintPath, path)
  97. conf.lazyPaths.insert(AbsoluteDir path, 0)
  98. proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) =
  99. var packages = newStringTable(modeStyleInsensitive)
  100. var pos = dir.len-1
  101. if dir[pos] in {DirSep, AltSep}: inc(pos)
  102. for k,p in os.walkDir(dir):
  103. if k == pcDir and p[pos] != '.':
  104. addPackage(conf, packages, p, info)
  105. for p in packages.chosen:
  106. addNimblePath(conf, p, info)
  107. proc nimblePath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) =
  108. addPathRec(conf, path.string, info)
  109. addNimblePath(conf, path.string, info)
  110. let i = conf.nimblePaths.find(path)
  111. if i != -1:
  112. conf.nimblePaths.delete(i)
  113. conf.nimblePaths.insert(path, 0)
  114. when isMainModule:
  115. proc v(s: string): Version = s.newVersion
  116. # #head is special in the sense that it's assumed to always be newest.
  117. doAssert v"1.0" < v"#head"
  118. doAssert v"1.0" < v"1.1"
  119. doAssert v"1.0.1" < v"1.1"
  120. doAssert v"1" < v"1.1"
  121. doAssert v"#aaaqwe" < v"1.1" # We cannot assume that a branch is newer.
  122. doAssert v"#a111" < v"#head"
  123. let conf = newConfigRef()
  124. var rr = newStringTable()
  125. addPackage conf, rr, "irc-#a111", unknownLineInfo
  126. addPackage conf, rr, "irc-#head", unknownLineInfo
  127. addPackage conf, rr, "irc-0.1.0", unknownLineInfo
  128. #addPackage conf, rr, "irc", unknownLineInfo
  129. #addPackage conf, rr, "another", unknownLineInfo
  130. addPackage conf, rr, "another-0.1", unknownLineInfo
  131. addPackage conf, rr, "ab-0.1.3", unknownLineInfo
  132. addPackage conf, rr, "ab-0.1", unknownLineInfo
  133. addPackage conf, rr, "justone-1.0", unknownLineInfo
  134. doAssert toSeq(rr.chosen) ==
  135. @["irc-#head", "another-0.1", "ab-0.1.3", "justone-1.0"]