Repository: nvim-lua/plenary.nvim
Pull request #138 (2021-04-16 14:53:32)

Paths
Normalized?

The real question is should I cache the computation or compute each time.  Currently I went with compute each time, just in case there is a reason not to cache it! (requires absolute path to run this)  That means if you call normalize you also must absolutize the path.  Not sure if you wanted that.


Patch:
diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua
index 8d7376d..1478008 100644
--- a/lua/plenary/path.lua
+++ b/lua/plenary/path.lua
@@ -59,6 +59,42 @@ local function is_root(pathname)
   return pathname == '/'
 end
 
+local _split_by_separator = (function()
+    local formatted =  string.format("([^%s]+)", path.sep)
+    return function(filepath)
+        local t = {}
+        for str in string.gmatch(filepath, formatted) do
+            table.insert(t, str)
+        end
+        return t
+    end
+end)()
+
+local function _normalize_path(filename)
+  local out_file = filename
+
+  local has = string.find(filename, "..", 1, true)
+
+  if has then
+      local parts = _split_by_separator(filename)
+
+      local idx = 1
+      repeat
+          if parts[idx] == ".." then
+              table.remove(parts, idx)
+              table.remove(parts, idx - 1)
+              idx = idx - 2
+          end
+          idx = idx + 1
+      until idx > #parts
+
+      out_file = table.concat(parts, path.sep)
+  end
+
+  return out_file
+end
+
+
 local clean = function(pathname)
   -- Remove double path seps, it's annoying
   pathname = pathname:gsub(path.sep .. path.sep, path.sep)
@@ -206,9 +242,9 @@ end
 
 function Path:absolute()
   if self:is_absolute() then
-    return self.filename
+    return _normalize_path(self.filename)
   else
-    return self._absolute or table.concat({self._cwd, self.filename}, self._sep)
+    return _normalize_path(self._absolute or table.concat({self._cwd, self.filename}, self._sep))
   end
 end
 
@@ -266,7 +302,7 @@ function Path:normalize(cwd)
   -- Substitute home directory w/ "~"
   self.filename = self.filename:gsub("^" .. path.home, '~', 1)
 
-  return self.filename
+  return _normalize_path(self.filename)
 end
 
 local shorten = (function()


Test patch:
diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua
index 4eb509a..9f0750e 100644
--- a/tests/plenary/path_spec.lua
+++ b/tests/plenary/path_spec.lua
@@ -24,6 +24,11 @@ describe('Path', function()
       local p = Path:new { "./home/tj/" , sep = '/'}
       assert(not p:is_absolute(), "Is absolute")
     end)
+
+    it('will normalize the path', function()
+      local p = Path:new { "lua", "..", "README.md" , sep = '/'}
+      assert.are.same(p:absolute(), vim.fn.fnamemodify("README.md", ":p"))
+    end)
   end)
 
   it('can join paths by constructor or join path', function()
@@ -164,6 +169,12 @@ describe('Path', function()
       local final = Path:new(orig):normalize()
       assert.are.same(final, 'lua/plenary/path.lua')
     end)
+
+    it('can remove the .. in paths', function()
+      local orig = 'lua//plenary/path.lua/foo/bar/../..'
+      local final = Path:new(orig):normalize()
+      assert.are.same(final, 'lua/plenary/path.lua')
+    end)
   end)
 
   describe('mkdir / rmdir', function()