Repository: nvim-lua/plenary.nvim
Pull request #130 (2021-04-09 14:07:39)

Use vim.v.progpath to spawn busted test runs (fixes stack overflow)
This supports setups where `nvim` is *not* a 0.5 nvim, but where the
test run is started using a different program path, for example using an  alias.

Previously `v --headless [...] -c "PlenaryBustedDirectory [...]"`
failed:

    testing: 	[...]/tests/manual/large_job_spec.lua
    ========================================
    FAILED TO LOAD FILE
    stack overflow
    ========================================

Here `v` is a alias pointing to a development build of neovim with
`$VIMRUNTIME` set appropriately.



Patch:
diff --git a/README.md b/README.md
index 458b1ca..1b671a3 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,6 @@ Plug 'nvim-lua/plenary.nvim'
 - `plenary.context_manager`
 - `plenary.test_harness`
 - `plenary.filetype`
-- `plenary.neorocks` (This may move to packer.nvim, but I have added some improvements to use it more as a library.)
 
 ### plenary.job
 
@@ -96,7 +95,7 @@ nmap <leader>t <Plug>PlenaryTestFile
 To run a whole directory from the command line, you could do something like:
 
 ```
-nvim --headless -c 'PlenaryBustedDirectory tests/plenary/ {minimal_init = "tests/minimal_init.vim"}'
+nvim --headless -c "PlenaryBustedDirectory tests/plenary/ {minimal_init = 'tests/minimal_init.vim'}"
 ```
 
 Where the first argument is the directory you'd like to test. It will search for files with
@@ -205,40 +204,5 @@ This will enable debuggin for the plugin.
 
 ### plenary.neorocks
 
-Install lua packages with `luarocks`!
+DELETED: Please use packer.nvim or other lua-rocks wrapper instead. This no longer exists.
 
-Include the following somewhere in your configuration (either heredoc or sourced lua file):
-
-```lua
-local neorocks = require("plenary.neorocks")
-
--- Installs neorocks if necessary and then installs the package you need.
---  Is not synchronous, so should be called from Neovim's command line most likely.
-neorocks.install('penlight', 'pl')
-
--- ensure_installed(package_name, lua_name, headless)
--- Only installs if currently not installed.
---
---   package_name : str             - Name of the package for luarocks
---   lua_name     : (optional) str  - Name of the package that you can require. Used to determine if we have it installed already (not from luarocks)
-neorocks.ensure_installed('penlight', 'pl')
-neorocks.ensure_installed('lua-cjson', 'cjson')
-```
-
-Inspiration: https://github.com/theHamsta/nvim_rocks . However, I've used quite a different end goal (following XDG_CONFIG standards, using `package.path` and `package.cpath` to load the packages and a different strategy of loading).
-
-
-### And more to come :)
-
-- [ ] Floating window wrappers
-- [ ] Easy border windows to any floating window
-
-
-
-### Used by:
-
-- [express_line.nvim](https://github.com/tjdevries/express_line.nvim)
-- [apyrori.nvim](https://github.com/tjdevries/apyrori.nvim)
-- [nlua.nvim](https://github.com/tjdevries/nlua.nvim)
-- (TODO) [popup.nvim](https://github.com/nvim-lua/popup.nvim)
-- (TODO) [telescope.nvim](https://github.com/nvim-lua/telescope.nvim)
diff --git a/lua/plenary/async_lib/async.lua b/lua/plenary/async_lib/async.lua
index 3fce4a5..2ff047d 100644
--- a/lua/plenary/async_lib/async.lua
+++ b/lua/plenary/async_lib/async.lua
@@ -175,6 +175,10 @@ M.void = function(async_func)
   end
 end
 
+M.async_void = function(func)
+  return M.void(M.async(func))
+end
+
 ---creates an async function
 ---@param func function
 ---@return function: returns an async function
diff --git a/lua/plenary/async_lib/lsp.lua b/lua/plenary/async_lib/lsp.lua
index 6f96354..237cc52 100644
--- a/lua/plenary/async_lib/lsp.lua
+++ b/lua/plenary/async_lib/lsp.lua
@@ -2,7 +2,14 @@ local a = require('plenary.async_lib.async')
 
 local M = {}
 
----Same as vim.lsp.buf_request but works with async await
+---This will be deprecated because the callback can be called multiple times.
+---This will give a coroutine error because the coroutine will be resumed multiple times.
+---Please use buf_request_all instead.
 M.buf_request = a.wrap(vim.lsp.buf_request, 4)
 
+--This was recently merged into master so we just check if it is there
+if vim.lsp.buf_request_all ~= nil then
+  M.buf_request_all = a.wrap(vim.lsp.buf_request_all, 4)
+end
+
 return M
diff --git a/lua/plenary/async_lib/util.lua b/lua/plenary/async_lib/util.lua
index b0d035c..d719c7d 100644
--- a/lua/plenary/async_lib/util.lua
+++ b/lua/plenary/async_lib/util.lua
@@ -127,9 +127,9 @@ end
 ---when a permit can be acquired returns it
 ---call permit:forget() to forget the permit
 Semaphore.acquire = a.wrap(function(self, callback)
-  self.permits = self.permits - 1
-
-  if self.permits <= 0 then
+  if self.permits > 0 then
+    self.permits = self.permits - 1
+  else
     table.insert(self.handles, callback)
     return
   end
@@ -140,9 +140,9 @@ Semaphore.acquire = a.wrap(function(self, callback)
     self.permits = self.permits + 1
 
     if self.permits > 0 and #self.handles > 0 then
+      self.permits = self.permits - 1
       local callback = table.remove(self.handles)
       callback(self_permit)
-      self.permits = self.permits - 1
     end
   end
 
diff --git a/lua/plenary/functional.lua b/lua/plenary/functional.lua
index f417ce7..9618e8e 100644
--- a/lua/plenary/functional.lua
+++ b/lua/plenary/functional.lua
@@ -1,14 +1,5 @@
 local f = {}
 
-function f.map(fun, iter)
-  local results = {}
-  for _, v in pairs(iter) do
-    table.insert(results, fun(v))
-  end
-
-  return results
-end
-
 function f.kv_pairs(t)
   local results = {}
   for k, v in pairs(t) do
@@ -32,9 +23,9 @@ function f.partial(fun, ...)
   end
 end
 
-function f.any(f, iterable)
+function f.any(fun, iterable)
   for k, v in pairs(iterable) do
-    if f(k, v) then
+    if fun(k, v) then
       return true
     end
   end
@@ -42,9 +33,9 @@ function f.any(f, iterable)
   return false
 end
 
-function f.all(f, iterable)
+function f.all(fun, iterable)
   for k, v in pairs(iterable) do
-    if not f(k, v) then
+    if not fun(k, v) then
       return false
     end
   end
diff --git a/lua/plenary/log.lua b/lua/plenary/log.lua
index e10e963..48798f3 100644
--- a/lua/plenary/log.lua
+++ b/lua/plenary/log.lua
@@ -51,7 +51,7 @@ local unpack = unpack or table.unpack
 log.new = function(config, standalone)
   config = vim.tbl_deep_extend("force", default_config, config)
 
-  local outfile = string.format('%s/%s.log', vim.api.nvim_call_function('stdpath', {'data'}), config.plugin)
+  local outfile = string.format('%s/%s.log', vim.api.nvim_call_function('stdpath', {'cache'}), config.plugin)
 
   local obj
   if standalone then
diff --git a/lua/plenary/neorocks/init.lua b/lua/plenary/neorocks/init.lua
index 7e1785a..a29f53c 100644
--- a/lua/plenary/neorocks/init.lua
+++ b/lua/plenary/neorocks/init.lua
@@ -1,430 +1,1 @@
-local Job = require('plenary.job')
-local Path = require('plenary.path')
-
-local log = require('plenary.log')
-local run = require('plenary.run')
-local window_float = require('plenary.window.float')
-
--- TODO: We should consider not making windows when headless.
-local headless = require('plenary.nvim_meta').is_headless
-
-local neorocks = {}
-
-neorocks.scheduler = require('plenary.neorocks.scheduler'):new()
-
-neorocks.job_with_display_output = function(title_text, command, args)
-  log.debug("Starting job:", title_text, command, args)
-
-  if type(title_text) == "string" then
-    title_text = {title_text}
-  end
-
-  if headless then
-    io.write(command .. '\n')
-    io.write(vim.inspect(args) .. '\n')
-  end
-
-  local outputter = vim.schedule_wrap(function(_, data, self)
-    if data == nil then
-      return
-    end
-
-    if not self then
-      return
-    end
-
-    if headless then
-      io.write(data .. '\n')
-      return
-    end
-
-    local bufnr = self.user_data.views.bufnr
-    local win_id = self.user_data.views.win_id
-
-    if not vim.api.nvim_buf_is_valid(bufnr) then
-      return
-    end
-
-    local split_data = vim.split(data, "\n")
-    if #split_data > 1 and split_data[#split_data] == "" then
-      split_data[#split_data] = nil
-    end
-
-    vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, split_data)
-
-    if not vim.api.nvim_win_is_valid(win_id) then
-      return
-    end
-
-    local final_row = #vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-    vim.api.nvim_win_set_cursor(win_id, {final_row, 0})
-  end)
-
-  return Job:new {
-    -- TODO: Should test if this splits or not... otherwise gotta do that annoyin thing
-    command = command,
-    args = args,
-
-    on_start = function(self)
-      self.user_data.views = window_float.centered_with_top_win(title_text, {winblend = 0})
-
-      vim.fn.win_gotoid(self.user_data.views.win_id)
-      vim.api.nvim_win_set_option(self.user_data.views.win_id, 'wrap', false)
-    end,
-
-    on_stdout = outputter,
-    on_stderr = outputter,
-
-    on_exit = vim.schedule_wrap(function(self, code)
-      local bufnr = self.user_data.views.bufnr
-      if not vim.api.nvim_buf_is_valid(bufnr) then return end
-
-      if code == 0 then
-        vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {"", ("="):rep(40), "  Success! <Enter> to close.", ("="):rep(40)})
-      else
-        vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, {"", ("="):rep(40), "  Failed to complete. <Enter> to close.", ("="):rep(40)})
-      end
-
-      local win_id = self.user_data.views.win_id
-      if not vim.api.nvim_win_is_valid(win_id) then return end
-
-      local final_row = #vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-      vim.api.nvim_win_set_cursor(win_id, {final_row, 0})
-
-      vim.api.nvim_buf_set_keymap(bufnr, 'n', '<CR>', ':quit<CR>', {silent = true, noremap = true})
-    end)
-  }
-end
-
----@return table Of the form: {
----     lua: Lua Version (5.1, 5.2, etc.)
----     jit: Jit Version (2.1.0-beta3, or nil)
----     dir: Directory of hererocks installation
---- }
-neorocks._lua_version = (function()
-  if jit then
-    return {
-      lua = string.gsub(_VERSION, "Lua ", ""),
-      jit = string.gsub(jit.version, "LuaJIT ", ""),
-      dir = string.gsub(jit.version, "LuaJIT ", "")
-    }
-  end
-
-  error("NEOROCKS: Unsupported Lua Versions", _VERSION)
-end)()
-
-neorocks._base_path                  = Path:new(vim.fn.stdpath('cache'), 'plenary_hererocks')
-neorocks._hererocks_file             = Path:new(vim.fn.stdpath('cache'), 'hererocks.py')
-neorocks._hererocks_install_location = Path:new(neorocks._base_path, neorocks._lua_version.dir)
-neorocks._is_setup                   = vim.fn.isdirectory(Path:new(neorocks._hererocks_install_location, "lib"):absolute()) > 0
-
-neorocks._get_hererocks_job = function()
-  local url_loc = 'https://raw.githubusercontent.com/luarocks/hererocks/latest/hererocks.py'
-
-  local command, args
-  if vim.fn.executable('curl') > 0 then
-    command = 'curl'
-    args = {url_loc, '-o', neorocks._hererocks_file:absolute()}
-  elseif vim.fn.executable('wget') > 0 then
-    command = 'wget'
-    args = {url_loc, '-O', neorocks._hererocks_file:absolute(), '--verbose'}
-  else
-    error('"curl" or "wget" is required')
-  end
-
-  return neorocks.job_with_display_output(
-    {"Installing hererocks"},
-    command,
-    args
-  )
-end
-
-neorocks._get_setup_job = function(force, opts)
-  local lua_version = neorocks._lua_version
-  local install_location = neorocks._hererocks_install_location
-
-  if force == nil then
-    force = false
-  end
-
-  if opts == nil then
-    opts = {}
-    opts.split = true
-    opts.wait = true
-  end
-
-  if neorocks._is_setup and not force then
-    return
-  end
-
-  if vim.fn.filereadable(neorocks._hererocks_file:absolute()) == 0 then
-    neorocks.scheduler:insert(neorocks._get_hererocks_job(opts))
-  end
-
-  if lua_version.jit then
-    return neorocks.job_with_display_output(
-      {"Installing luajit & luarocks"},
-      -- TODO: This should be a user specified python, just in case?
-      "python",
-      {
-        neorocks._hererocks_file:absolute(),
-        "--verbose",
-        force and "-i" or "",
-        "-j",
-        lua_version.jit,
-        "-r",
-        "latest",
-        install_location:absolute()
-      }
-    )
-  else
-    error("Unsupported version")
-  end
-end
-
-neorocks.setup = function(force, quit)
-  vim.fn.mkdir(neorocks._base_path:absolute(), "p")
-  quit = (quit == nil and true) or quit
-  neorocks.scheduler:insert(neorocks._get_setup_job(force))
-
-  if quit then
-    neorocks.scheduler:insert {
-      start = function()
-        vim.cmd [[qa!]]
-      end,
-
-      add_on_exit_callback = function()
-      end,
-    }
-  end
-end
-
-neorocks._get_package_paths = function()
-  local lua_version = neorocks._lua_version
-  local install_location = neorocks._hererocks_install_location
-
-  local install_path = install_location:joinpath(
-    "lib",
-    "luarocks",
-    string.format("rocks-%s", lua_version.lua)
-  )
-
-  local share_path = install_location:joinpath(
-    "share",
-    "lua",
-    lua_version.lua
-  )
-
-  local gen_pattern = function(directory)
-    return string.format(
-    "%s/?.lua;%s/?/init.lua",
-    directory,
-    directory
-  )
-  end
-
-  return gen_pattern(share_path:absolute()) .. ';' .. gen_pattern(install_path:absolute())
-end
-
---- Idempotent adding of paths for both package.path and package.cpath
-neorocks.setup_paths = function()
-  if not neorocks._is_setup then
-    return
-  end
-
-  if neorocks._path_setup_complete then
-    return
-  end
-
-  local lua_version = neorocks._lua_version
-  local install_location = neorocks._hererocks_install_location
-
-  local match_install_path = neorocks._get_package_paths()
-
-  if not string.find(package.path, match_install_path, 1, true) then
-    package.path = package.path .. ';' .. match_install_path
-  end
-
-  local install_cpath = install_location:joinpath("lib", "lua", lua_version.lua)
-  local match_install_cpath = string.format(
-    "%s/?.so",
-    install_cpath:absolute()
-  )
-  if not string.find(package.cpath, match_install_cpath, 1, true) then
-    package.cpath = package.cpath .. ';' .. match_install_cpath
-  end
-
-  neorocks._path_setup_complete = true
-end
-
--- activate hererocks based on current $SHELL
-local function source_activate(install_location, activate_file)
-  return string.format('source %s', install_location:joinpath('bin', activate_file):absolute())
-end
-
---- Get the string to source hererocks
-neorocks._source_string = function(install_location)
-  local user_shell = os.getenv("SHELL") or "bash"
-  local shell = user_shell:gmatch("([^/]*)$")()
-  if shell == "fish" then
-    return source_activate(install_location, 'activate.fish')
-  elseif shell == "csh" then
-    return source_activate(install_location, 'activate.csh')
-  end
-  return source_activate(install_location, 'activate')
-end
-
-neorocks._luarocks_exec = function(luarocks_arg)
-  local install_location = neorocks._hererocks_install_location
-
-  return vim.fn.systemlist(string.format(
-    "%s && luarocks %s",
-    neorocks._source_string(install_location),
-    luarocks_arg
-  ))
-end
-
---- Properly source and run a luarocks command. Will run `luarocks $luarocks_arg`
-neorocks._luarocks_run = function(luarocks_arg)
-  local install_location = neorocks._hererocks_install_location
-  local source_string = neorocks._source_string(install_location)
-
-  run.with_displayed_output(
-    {"Lua rocks install"},
-    string.format(
-      '%s && luarocks %s',
-      source_string,
-      luarocks_arg
-    ),
-    {
-      split = true,
-      wait = true
-    }
-  )
-end
-
---- Properly source and return the output of a luarocks command.
-neorocks._get_luarocks_job = function(luarocks_arg)
-  local install_location = neorocks._hererocks_install_location
-  local source_string = neorocks._source_string(install_location)
-
-  local command = "bash"
-  local args = {"-c", string.format('%s && luarocks %s', source_string, luarocks_arg)}
-
-  return neorocks.job_with_display_output("Luarocks: " .. luarocks_arg, command, args)
-end
-
-
-neorocks._get_install_job = function(package_name)
-  return neorocks._get_luarocks_job(string.format('install %s', package_name))
-end
-
-neorocks.install = function(package_name, lua_name, force, should_quit)
-  neorocks.scheduler:insert(neorocks._get_setup_job())
-
-  if not force and neorocks.is_package_installed(package_name, lua_name) then
-    print(package_name, 'is already installed!')
-    return
-  end
-
-  local install_job = neorocks.scheduler:insert(neorocks._get_install_job(package_name))
-
-  if headless and should_quit == nil then
-    should_quit = true
-  end
-
-  if should_quit then
-    neorocks.scheduler:insert {
-      start = function()
-        vim.cmd [[qa!]]
-      end,
-
-      add_on_exit_callback = function()
-      end,
-    }
-  end
-
-  return install_job
-end
-
-neorocks.ensure_installed = function(package_name, lua_name)
-  -- Don't try and install on startup. It's annoying.
-  -- Maybe someday we can do it.
-  if not neorocks._is_setup then
-    vim.api.nvim_err_writeln("Neorocks is not yet set up. Please manually install")
-    return
-  end
-
-  neorocks.setup_paths()
-
-  if lua_name == nil then
-    lua_name = package_name
-  end
-
-  if neorocks.is_package_installed(package_name, lua_name) then
-    return
-  end
-
-  neorocks.install(package_name, lua_name):wait(60000, 100, true)
-end
-
-neorocks.remove = function(package_name)
-  neorocks._luarocks_run(string.format('remove %s', package_name))
-end
-
-neorocks.list = function(package_filter)
-  if package_filter == nil then
-    package_filter = ''
-  end
-
-  local result = {}
-  for _, line in ipairs(
-    neorocks._luarocks_exec(
-      string.format('list %s --porcelain', package_filter),
-      true
-    )
-  ) do
-    for l_package, version, status, install_path in string.gmatch(line, "([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)") do
-      table.insert(result, {
-        l_package = l_package,
-        version = version,
-        status = status,
-        install_path = install_path
-      })
-    end
-  end
-
-  return result
-end
-
-neorocks.is_package_installed = function(package_name, lua_name)
-  -- TODO: Decided if I need anything more than this.
-  -- This tells me if it's available or not, which is really all I care about.
-  if lua_name == nil then
-    lua_name = package_name
-  end
-
-  if package.loaded[package_name] then
-    return true
-  end
-
-  local can_import, _ = pcall(function() return require(lua_name) end)
-  if can_import then
-    return true
-  end
-
-  local options = neorocks.list(package_name)
-
-  for _, p in ipairs(options) do
-    if p.l_package == package_name then
-      return true
-    end
-  end
-
-  return false
-end
-
--- package.searchpath(${1:name: string}, ${2:path: string}, ${3:sep: string}, ${4:rep: string})
--- package.searchpath("pl", package.path)
-
-return neorocks
+error("neorocks is no longer supported. Please use packer.nvim or other project for neorocks usage.")
diff --git a/lua/plenary/neorocks/scheduler.lua b/lua/plenary/neorocks/scheduler.lua
deleted file mode 100644
index 4127e71..0000000
--- a/lua/plenary/neorocks/scheduler.lua
+++ /dev/null
@@ -1,44 +0,0 @@
-local scheduler = {}
-
-function scheduler:new()
-  return setmetatable({
-    items = {},
-
-    in_progress = nil,
-  }, {
-    __index = self,
-  })
-end
-
-function scheduler:insert(item)
-  if not item then
-    return item
-  end
-
-  table.insert(self.items, item)
-
-  if not self.in_progress then
-    self:_run_item(item)
-  else
-    self:_chain_item(item)
-  end
-
-  return item
-end
-
-function scheduler:_run_item(item)
-  self.in_progress = true
-  item:add_on_exit_callback(function()
-    self.in_progress = false
-  end)
-
-  item:start()
-end
-
-function scheduler:_chain_item(item)
-  self.items[#self.items - 1]:add_on_exit_callback(vim.schedule_wrap(function()
-    self:_run_item(item)
-  end))
-end
-
-return scheduler
diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua
index 8d7376d..2de31c2 100644
--- a/lua/plenary/path.lua
+++ b/lua/plenary/path.lua
@@ -7,8 +7,6 @@ local uv = vim.loop
 
 local F = require('plenary.functional')
 
-
-
 local S_IF = {
   -- S_IFDIR  = 0o040000  # directory
   DIR = 0x4000,
@@ -59,6 +57,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 = path.root(filename) .. 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 +240,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,11 +300,32 @@ 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 function shorten_len(filename, len)
+  local final_match
+  local final_path_components = {}
+  for match in (filename..path.sep):gmatch("(.-)"..path.sep) do
+    if #match > len then
+      table.insert(final_path_components, string.sub(match, 1, len))
+    else
+      table.insert(final_path_components, match)
+    end
+    table.insert(final_path_components, path.sep)
+    final_match = match
+  end
+
+  local l = #final_path_components -- so that we don't need to keep calculating length
+  table.remove(final_path_components, l) -- remove final slash
+  table.remove(final_path_components, l-1) -- remvove shortened final component
+  table.insert(final_path_components, final_match) -- insert full final component
+
+  return table.concat(final_path_components)
 end
 
 local shorten = (function()
-  if jit then
+  if jit and path.sep ~= '\\' then
     local ffi = require('ffi') ffi.cdef [[
     typedef unsigned char char_u;
     char_u *shorten_dir(char_u *str);
@@ -286,11 +341,15 @@ local shorten = (function()
     end
   end
   return function(filename)
-    return filename
+    shorten_len(filename, 1)
   end
 end)()
 
-function Path:shorten()
+function Path:shorten(len)
+  assert(len ~= 0, 'len must be at least 1')
+  if len and len > 1 then
+    return shorten_len(self.filename, len)
+  end
   return shorten(self.filename)
 end
 
diff --git a/lua/plenary/tbl.lua b/lua/plenary/tbl.lua
index d1f018c..44de295 100644
--- a/lua/plenary/tbl.lua
+++ b/lua/plenary/tbl.lua
@@ -24,5 +24,17 @@ function tbl.unpack(t, i, j)
   return unpack(t, i or 1, j or t.n or #t)
 end
 
+---Freeze a table. A frozen table is not able to be modified.
+---http://lua-users.org/wiki/ReadOnlyTables
+---@param t table
+---@return table
+function tbl.freeze(t)
+  return setmetatable({}, {
+    __index = t,
+    __newindex = function()
+      error("Attempt to modify frozen table")
+    end
+  })
+end
 
 return tbl
diff --git a/plugin/plenary.vim b/plugin/plenary.vim
index a6ae381..d0ac14b 100644
--- a/plugin/plenary.vim
+++ b/plugin/plenary.vim
@@ -1,7 +1,4 @@
 
-" Set up neorocks if it is installed.
-lua pcall(function() require('plenary.neorocks').setup_paths() end)
-
 " Create command for running busted
 command! -nargs=1 -complete=file PlenaryBustedFile
       \ lua require('plenary.busted').run(vim.fn.expand("<args>"))
diff --git a/scratch/neorocks_thoughts.txt b/scratch/neorocks_thoughts.txt
deleted file mode 100644
index b5dfcd1..0000000
--- a/scratch/neorocks_thoughts.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-
-# Happy path
-neorocks.ensure_installed
--> oh hey, neorocks is setup (does nothing)
--> oh hey, already installed
-
-
-# Worst path
-neorocks.install
--> no hererocks.
-    -> wget / curl hererocks
-    -> build / install lua & luarocks
-    -> OK, I'm done
--> once install is done
-    -> build / install the lua package
--> return control flow back?
-
-# First version is, you call install.
-# Opens up window
-# You see that it's doing stuff
-# So you wait (as a person) for it to be done
-# Then you require it.
-
-# Second version is, somehow we do this during startup
-# But still allow you to complete startup w/out freezing for a long time
-
-# Alternative version:
-# Run the install w/ headless nvim
-# Could make a script, available in plenary/scripts or something like that, that allows you to do so.
- nvim --headless -c "lua require('plenary.neorocks').install('lua-cjson')" -c "qa"


Test patch:
diff --git a/lua/plenary/test_harness.lua b/lua/plenary/test_harness.lua
index 1c4bf46..35caf63 100644
--- a/lua/plenary/test_harness.lua
+++ b/lua/plenary/test_harness.lua
@@ -67,7 +67,7 @@ function harness.test_directory(directory, opts)
 
   local path_len = #paths
 
-  local jobs = f.map(
+  local jobs = vim.tbl_map(
     function(p)
       local args = {
         '--headless',
@@ -83,7 +83,7 @@ function harness.test_directory(directory, opts)
       end
 
       return Job:new {
-        command = 'nvim',
+        command = vim.v.progpath,
         args = args,
 
         -- Can be turned on to debug
@@ -145,7 +145,7 @@ function harness._find_files_to_run(directory)
     args = {directory, '-type', 'f', '-name', '*_spec.lua'},
   }
 
-  return f.map(Path.new, finder:sync())
+  return vim.tbl_map(Path.new, finder:sync())
 end
 
 function harness._run_path(test_type, directory)
diff --git a/scratch/here_rock_tester.lua b/scratch/here_rock_tester.lua
deleted file mode 100644
index 21e7c28..0000000
--- a/scratch/here_rock_tester.lua
+++ /dev/null
@@ -1,11 +0,0 @@
-
-require('plenary.reload').reload_module('plenary')
-
--- local rocks = require('nvim_rocks')
--- rocks.ensure_installed('lua-cjson')
--- require('cjson')
-
-local neorocks = require('plenary.neorocks')
-
-neorocks.install('lua-cjson')
--- neorocks.remove('busted')
diff --git a/scratch/test_neorock_stuff.lua b/scratch/test_neorock_stuff.lua
deleted file mode 100644
index 0c71622..0000000
--- a/scratch/test_neorock_stuff.lua
+++ /dev/null
@@ -1,19 +0,0 @@
-require('plenary.reload').reload_module('plenary')
-
--- TODO: We could add some stuff to delete this at the beginning to start fresh.
--- Probably would be useful to have an uninstall anyways
-
-local neorocks = require('plenary.neorocks')
-neorocks.install('luasocket', nil, true)
-
--- local job = neorocks._get_setup_job(false)
--- neorocks.ensure_installed('effil', 'effil')
--- neorocks.ensure_installed('lua-cjson')
--- neorocks.ensure_installed('luasocket')
-
--- if job then
---   job:start()
--- else
---   print("Already installed")
--- end
-
diff --git a/tests/plenary/async_lib/semaphore_spec.lua b/tests/plenary/async_lib/semaphore_spec.lua
index f2908fb..f3df6a4 100644
--- a/tests/plenary/async_lib/semaphore_spec.lua
+++ b/tests/plenary/async_lib/semaphore_spec.lua
@@ -12,6 +12,47 @@ a.describe('semaphore', function()
     eq(status, false)
   end)
 
-  a.it('should count properly', function()
+  a.it('should acquire a permit if available', function ()
+    local sem = a.util.Semaphore.new(1)
+    local permit = await(sem:acquire())
+    assert(permit ~= nil)
+  end)
+
+  a.it('should block if no permit is available', function ()
+    local sem = a.util.Semaphore.new(1)
+    await(sem:acquire())
+    
+    local completed = false
+    local blocking = async(function ()
+      await(sem:acquire())
+      completed = true
+    end)
+    a.run(blocking())
+
+    eq(completed, false)
+  end)
+
+  a.it('should give another permit when an acquired permit is released', function ()
+    local sem = a.util.Semaphore.new(1)
+    local permit = await(sem:acquire())
+    permit:forget()
+    local next_permit = await(sem:acquire())
+    assert(next_permit ~= nil)
+  end)
+
+  a.it('should permit the next waiting client when a permit is released', function () 
+    local sem = a.util.Semaphore.new(1)
+    local permit = await(sem:acquire())
+
+    local completed = false
+    local blocking = async(function ()
+      await(sem:acquire())
+      completed = true
+    end)
+
+    a.run(blocking())
+    permit:forget()
+
+    eq(completed, true)
   end)
 end)
diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua
index 4eb509a..571d89f 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()
@@ -153,16 +158,48 @@ describe('Path', function()
 
   describe(':normalize', function()
     it('can take paths with double separators change them to single separators', function()
-      local orig = 'lua//plenary/path.lua'
+      local orig = '/lua//plenary/path.lua'
       local final = Path:new(orig):normalize()
-      assert.are.same(final, 'lua/plenary/path.lua')
+      assert.are.same(final, '/lua/plenary/path.lua')
     end)
     -- this may be redundant since normalize just calls make_relative which is tested above
     it('can take absolute paths with double seps'
       .. 'and make them relative with single seps', function()
-      local orig = vim.loop.cwd() .. '/lua//plenary/path.lua'
+      local orig = '/lua//plenary/path.lua'
+      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')
+      assert.are.same(final, '/lua/plenary/path.lua')
+    end)
+  end)
+
+  describe(':shorten', function()
+    it('can shorten a path', function()
+      local long_path = '/this/is/a/long/path'
+      local short_path = Path:new(long_path):shorten()
+      assert.are.same(short_path, '/t/i/a/l/path')
+    end)
+  end)
+
+  describe(':shorten', function()
+    it('can shorten a path components to a given length', function()
+      local long_path = '/this/is/a/long/path'
+      local short_path = Path:new(long_path):shorten(2)
+      assert.are.same(short_path, '/th/is/a/lo/path')
+
+      -- without the leading /
+      long_path = 'this/is/a/long/path'
+      short_path = Path:new(long_path):shorten(3)
+      assert.are.same(short_path, 'thi/is/a/lon/path')
+
+      -- where len is greater than the length of the final component
+      long_path = 'this/is/an/extremely/long/path'
+      short_path = Path:new(long_path):shorten(5)
+      assert.are.same(short_path, 'this/is/an/extre/long/path')
     end)
   end)
 
diff --git a/tests/plenary/tbl_spec.lua b/tests/plenary/tbl_spec.lua
new file mode 100644
index 0000000..60faa18
--- /dev/null
+++ b/tests/plenary/tbl_spec.lua
@@ -0,0 +1,27 @@
+local tbl = require('plenary.tbl')
+
+local function should_fail(fun)
+  local stat = pcall(fun)
+  assert(not stat, "Function should have errored")
+end
+
+describe('tbl utilities', function()
+  it('should be able to freeze a table', function()
+    local t = {1, 2, 3}
+    local frozen = tbl.freeze(t)
+    assert(t[1] == frozen[1])
+    assert(t[2] == frozen[2])
+    assert(t[3] == frozen[3])
+
+    should_fail(function()
+      frozen[4] = "thisthis"
+    end)
+
+    should_fail(function()
+      frozen.hello = "asdfasdf"
+    end)
+
+    assert(not frozen[5])
+    assert(not frozen.hello)
+  end)
+end)