summary refs log tree commit diff stats
path: root/compiler/nimblecmd.nim
blob: 8305b01f539635d7a5c917c7cb3dcf4a3e389823 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#
#
#           The Nim Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Implements some helper procs for Nimble (Nim's package manager) support.

import parseutils, strutils, strtabs, os, options, msgs, sequtils,
  configuration

proc addPath*(conf: ConfigRef; path: string, info: TLineInfo) =
  if not conf.searchPaths.contains(path):
    conf.searchPaths.insert(path, 0)

type
  Version* = distinct string

proc `$`*(ver: Version): string {.borrow.}

proc newVersion*(ver: string): Version =
  doAssert(ver.len == 0 or ver[0] in {'#', '\0'} + Digits,
           "Wrong version: " & ver)
  return Version(ver)

proc isSpecial(ver: Version): bool =
  return ($ver).len > 0 and ($ver)[0] == '#'

proc isValidVersion(v: string): bool =
  if v.len > 0:
    if v[0] in {'#'} + Digits: return true

proc `<`*(ver: Version, ver2: Version): bool =
  ## This is synced from Nimble's version module.

  # Handling for special versions such as "#head" or "#branch".
  if ver.isSpecial or ver2.isSpecial:
    if ver2.isSpecial and ($ver2).normalize == "#head":
      return ($ver).normalize != "#head"

    if not ver2.isSpecial:
      # `#aa111 < 1.1`
      return ($ver).normalize != "#head"

  # Handling for normal versions such as "0.1.0" or "1.0".
  var sVer = string(ver).split('.')
  var sVer2 = string(ver2).split('.')
  for i in 0..max(sVer.len, sVer2.len)-1:
    var sVerI = 0
    if i < sVer.len:
      discard parseInt(sVer[i], sVerI)
    var sVerI2 = 0
    if i < sVer2.len:
      discard parseInt(sVer2[i], sVerI2)
    if sVerI < sVerI2:
      return true
    elif sVerI == sVerI2:
      discard
    else:
      return false

proc getPathVersion*(p: string): tuple[name, version: string] =
  ## Splits path ``p`` in the format ``/home/user/.nimble/pkgs/package-0.1``
  ## into ``(/home/user/.nimble/pkgs/package, 0.1)``
  result.name = ""
  result.version = ""

  const specialSeparator = "-#"
  var sepIdx = p.find(specialSeparator)
  if sepIdx == -1:
    sepIdx = p.rfind('-')

  if sepIdx == -1:
    result.name = p
    return

  for i in sepIdx..<p.len:
    if p[i] in {DirSep, AltSep}:
      result.name = p
      return

  result.name = p[0 .. sepIdx - 1]
  result.version = p.substr(sepIdx + 1)

proc addPackage(conf: ConfigRef; packages: StringTableRef, p: string; info: TLineInfo) =
  let (name, ver) = getPathVersion(p)
  if isValidVersion(ver):
    let version = newVersion(ver)
    if packages.getOrDefault(name).newVersion < version or
      (not packages.hasKey(name)):
      packages[name] = $version
  else:
    localError(conf, info, "invalid package name: " & p)

iterator chosen(packages: StringTableRef): string =
  for key, val in pairs(packages):
    let res = if val.len == 0: key else: key & '-' & val
    yield res

proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) =
  var path = p
  let nimbleLinks = toSeq(walkPattern(p / "*.nimble-link"))
  if nimbleLinks.len > 0:
    # If the user has more than one .nimble-link file then... we just ignore it.
    # Spec for these files is available in Nimble's readme:
    # https://github.com/nim-lang/nimble#nimble-link
    let nimbleLinkLines = readFile(nimbleLinks[0]).splitLines()
    path = nimbleLinkLines[1]
    if not path.isAbsolute():
      path = p / path

  if not contains(conf.searchPaths, path):
    message(conf, info, hintPath, path)
    conf.lazyPaths.insert(path, 0)

proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) =
  var packages = newStringTable(modeStyleInsensitive)
  var pos = dir.len-1
  if dir[pos] in {DirSep, AltSep}: inc(pos)
  for k,p in os.walkDir(dir):
    if k == pcDir and p[pos] != '.':
      addPackage(conf, packages, p, info)
  for p in packages.chosen:
    addNimblePath(conf, p, info)

proc nimblePath*(conf: ConfigRef; path: string, info: TLineInfo) =
  addPathRec(conf, path, info)
  addNimblePath(conf, path, info)

when isMainModule:
  proc v(s: string): Version = s.newVersion
  # #head is special in the sense that it's assumed to always be newest.
  doAssert v"1.0" < v"#head"
  doAssert v"1.0" < v"1.1"
  doAssert v"1.0.1" < v"1.1"
  doAssert v"1" < v"1.1"
  doAssert v"#aaaqwe" < v"1.1" # We cannot assume that a branch is newer.
  doAssert v"#a111" < v"#head"

  var rr = newStringTable()
  addPackage rr, "irc-#a111"
  addPackage rr, "irc-#head"
  addPackage rr, "irc-0.1.0"
  addPackage rr, "irc"
  addPackage rr, "another"
  addPackage rr, "another-0.1"

  addPackage rr, "ab-0.1.3"
  addPackage rr, "ab-0.1"
  addPackage rr, "justone"

  doAssert toSeq(rr.chosen) ==
    @["irc-#head", "another-0.1", "ab-0.1.3", "justone"]