Path = {} function Path:new(p) local is_absolute = p:sub(1, 1) == "/" p = p:gsub("/+", "/") p = p:gsub("(/%./", "/") p = p:gsub("/%.$", "") p = p:gsub("/$", "") p = p:gsub("^%./", "") p = p:gsub("/[^/]+/%.%.", "") local path = { segments = p:split('/'), is_absolute = is_absolute } setmetatable(path, self) self.__index = self return path end function Path:name() return self.segments[#self.segments] end function Path:stem() return self.segments[#self.segments]:match('[^.]+') end function Path:parent() return self / ".." end function Path:__tostring() return (self.is_absolute and "/" or "") .. table.concat(self.segments, "/") end function Path:__div(next) if next.is_absolute then error("cannot concat with absolute path") end return Path:new(self:__tostring() .. "/" .. tostring(next)) end function Path:open(mode) return io.open(tostring(self), mode) end function Path:exists() local f = self:open('r') if f ~= nil then f:close() return true end return false end function Path:is_directory() local f = self:open('r') if not f then return false end local value, err, code = f:read() f:close() return code == 21 end function Path:entries() if not self:is_directory() then return function() end end local pipe = io.popen('ls' .. ' "' .. tostring(self) .. '"') local lines = pipe:lines() return function() local value = lines() if value == nil then pipe:close() return nil end return self / value end end function Path:children() if not self:is_directory() then return function() end end local pipe = io.popen('find' .. ' "' .. tostring(self) .. '"') local lines = pipe:lines() return function() local value = lines() if value == nil then pipe:close() return nil end return Path:new(value) end end function Path:make_directory(opts) local pipe = io.popen('mkdir ' .. (opts.create_parents and '-p ' or ' ') .. tostring(self)) pipe:close() end return { tests = { function() local path = Path:new("some/path/here") assert.equals("here", path:name()) end, function() local path = Path:new("some/path/here.txt") assert.equals("here", path:stem()) end, function() local path = Path:new("some/path/here.txt") assert.equals("some/path", path:parent():__tostring()) end, function() local path = Path:new("some/path/here.txt") assert.equals("some/path/here.txt", path:__tostring()) end, function() local path = Path:new("some/path/here") path = path / 'next.txt' assert.equals("some/path/here/next.txt", path:__tostring()) end, function() local path = Path:new("./src") assert.equals(true, path:is_directory()) end, function() local path = Path:new("./src/blueprint") assert.equals(false, path:is_directory()) end, function() local path = Path:new("./src/blueprint") assert.equals(true, path:exists()) end, function() local path = Path:new("./src/blueprint__") assert.equals(false, path:exists()) end } }