summaryrefslogtreecommitdiffstats
path: root/aconf/path.lua
blob: 8091a359954abd226ab3140f7787169b25a085af (plain)
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
--[[
Copyright (c) 2012-2016 Kaarle Ritvanen
See LICENSE file for license details
--]]

--- @module aconf.model
local M = {}

local map = require('aconf.util').map

M.up = {symbol='..'}
M.wildcard = {symbol='*'}
local special = {['..']=M.up, ['*']=M.wildcard}


function M.is_absolute(path) return path:sub(1, 1) == '/' end


function M.escape(comp)
   if type(comp) == 'table' then
      assert(comp.symbol)
      return comp.symbol
   end
   if type(comp) == 'number' then return tostring(comp) end
   local res = comp:gsub('([\\/])', '\\%1')
   return (special[res] or tonumber(res)) and '\\'..res or res
end


--- joins a number of already escaped path components into a single
--- path.
-- @function path.rawjoin
-- @tparam string p1 path component
-- @tparam ?string p2 path component
-- @tparam ?string ... path component
-- @treturn string path
function M.rawjoin(p1, p2, ...)
   if not p2 then return p1 end
   if not M.is_absolute(p2) then p2 = '/'..p2 end
   return M.rawjoin((p1 == '/' and '' or p1)..p2, ...)
end

--- appends a number of additional components to a path. The appended
-- components are escaped first.
-- @function path.join
-- @tparam string parent path
-- @tparam ?string ... path component
-- @treturn string path
function M.join(parent, ...)
   local args = map(function(c) return M.escape(c) end, {...})
   if parent > '' then table.insert(args, 1, parent) end
   return M.rawjoin(table.unpack(args))
end


function M.split(path)
   local res = {}
   local comp = ''
   local escaped

   local function merge(s)
      if s > '' then
	 table.insert(res, not escaped and (special[s] or tonumber(s)) or s)
      end
   end

   while true do
      local prefix, sep, suffix = path:match('([^\\/]*)([\\/])(.*)')
      if not prefix then
	 merge(comp..path)
	 return res
      end

      comp = comp..prefix
      if sep == '\\' then
	 comp = comp..suffix:sub(1, 1)
	 escaped = true
	 path = suffix:sub(2, -1)
      else
	 merge(comp)
	 comp = ''
	 escaped = false
	 path = suffix
      end
   end
end


function M.is_unique(path)
   for _, comp in ipairs(M.split(path)) do
      if comp == M.wildcard then return false end
   end
   return true
end

function M.is_subordinate(p1, p2)
   p1 = M.split(p1)
   for i, comp in ipairs(M.split(p2)) do
      if p1[i] ~= comp then return false end
   end
   return true
end


function M.to_absolute(path, base)
   if not M.is_absolute(path) then
      path = base..(base ~= '/' and '/' or '')..path
   end
   local comps = M.split(path)
   local i = 1
   while i <= #comps do
      if comps[i] == M.up then
	 if i == 1 then error('Invalid path: '..path) end
	 table.remove(comps, i - 1)
	 table.remove(comps, i - 1)
	 i = i - 1
      else i = i + 1 end
   end
   return M.join('/', table.unpack(comps))
end


function M.parent(path)
   local comps = M.split(path)
   table.remove(comps)
   return M.join('/', table.unpack(comps))
end

function M.name(path)
   local comps = M.split(path)
   return comps[#comps]
end


return M