local ffi = require("ffi") local dirtools = {} local tinsert = table.insert local tconcat = table.concat --------------------------------------------------------------------------------------- -- Dirtools is about to become a little more complex and become a Virtual File System. -- -- Scope: The VFS will handle all file management, thus io calls should be replaced -- with the equivalent vfs calls (same name, different lib). -- There are unique examples where this might not be needed, this is why the io -- lib is not being overridden. -- -- File Support: VFS will handle unique file types and resource conversion, which may -- end up a separate module. Initially file types supported by the builder will be: -- - image files (png, jpg) -- - lua scripts (.lua and .lcc bytecode) -- - shaders (only .glsl - these will be converted to bytecode at build) -- - data files (extension must be registered with vfs, and custom handler can be added) -- -- Resrouces: VFS is most important for building and packaging resource for release -- creation. The build tool (rbuilder) shall have options for how resources are -- packaged. For example: Various custom data file types may need to exist -- standalone, and not be packaged into the resource files. -- -- Require: VFS will manage loading lua and modules via search_packages system in lua. -- If an example or project runs from a sokol-luajit repo then the build system -- shall replicate the same loading within the bytecode generation. -- Build script combine, build and this file dirtools will include functions to handle -- the specific pathing and file types as part of the require system. --------------------------------------------------------------------------------------- local allfolders_cmd = [[dir /ON /AD /B "%s"]] if(ffi.os ~= "Windows") then allfolders_cmd = [[ls -p "%s" | grep /]] end local allfiles_cmd = [[dir /ON /A-D /B "%s"]] if(ffi.os ~= "Windows") then allfiles_cmd = [[ls -p "%s" | grep -v /]] end -- -------------------------------------------------------------------------------------- local list_folders_cache = {} local list_cache = {} local sep = "\\" if(ffi.os ~= "Windows") then sep = "/" end dirtools.sep = sep --------------------------------------------------------------------------------------- dirtools.add_default_paths = function(path) local folders = { ["Linux"] = "linux", ["Windows"] = "win64", ["MacOSX"] = "macos", ["OSX"] = "macos", } local extensions = { ["Linux"] = "so", ["Windows"] = "dll", ["MacOSX"] = "so", ["OSX"] = "so", } package.cpath = package.cpath..";"..path.."bin/"..folders[ffi.os].."/?."..extensions[ffi.os] package.path = package.path..";"..path.."/ffi/sokol/?.lua" package.path = package.path..";"..path.."lua/?.lua" package.path = package.path..";"..path.."lua/?/init.lua" package.path = package.path..";"..path.."/?.lua" end --------------------------------------------------------------------------------------- local function log_info( str ) print(string.format("[Info] %s", str)) end local function log_error( str ) print(string.format("[Error] %s", str)) end --------------------------------------------------------------------------------------- dirtools.add_cpath = function( new_path ) package.cpath = package.cpath..";"..new_path end --------------------------------------------------------------------------------------- dirtools.add_package_path = function( new_path ) package.path = package.path..";"..new_path.."/?.lua" end --------------------------------------------------------------------------------------- dirtools.compare_paths = function(p1, p2) -- Remove minus, they seem to cause the most problems p1 = string.gsub(p1, "%-", "_") p2 = string.gsub(p2, "%-", "_") return string.match(p1, p2) end --------------------------------------------------------------------------------------- dirtools.combine_path = function( base, addition ) -- remove any returns and tabs from both base = string.gsub(base, "[\n\t\r]+","") addition = string.gsub(addition, "[\n\t\r]+","") return string.format("%s%s%s",base,sep,addition) end --------------------------------------------------------------------------------------- dirtools.get_drives = function() local drives = {} if(ffi.os == "Windows") then local cmd = "fsutil fsinfo drives" local fh = io.popen(cmd, "r") if(fh) then local data = fh:read("*a") fh:close() if(string.match(data, "Drives: ")) then data = string.sub(data, 10, -1) for f in string.gmatch(data, "([^%s]+)") do f = string.sub(f, 1, -2) tinsert(drives, f) end end end else -- Note: Add more -t to support different file system mounts local cmd = "df -h -t ext4 --output=target" local fh = io.popen(cmd, "r") if(fh) then local data = fh:read("*a") local count = 0 for f in string.gmatch(data, "(.-)\n") do f = string.gsub(f, "%s+", "") if(count > 0) then if(#f > 0) then tinsert(drives, f) end end count = count + 1 end fh:close() end end return drives end --------------------------------------------------------------------------------------- -- Much safer way to build folder than pattern match (very unstable) dirtools.get_folder = function(path) local parts = {} local patt = "(.-)[\\]" if(ffi.os ~= "Windows") then patt = "(.-)[/]" end for pseg in string.gmatch(path, patt) do tinsert(parts, pseg) end if(#parts < 1) then return path end return tconcat(parts, sep) end --------------------------------------------------------------------------------------- -- Check if the path is folder or file if(ffi.os == "Windows") then dirtools.is_folder = function(path) if(path == nil) then return nil end local fh = io.popen([[attrib "]]..path..[["]], "r") if(fh) then local res = fh:read("*a") local fileattr = string.sub(res, 1, 20) fileattr = string.gsub(fileattr, " ", "") fh:close() if(string.match(res, "^File not found")) then return nil end return (#fileattr == 0) end return nil end else dirtools.is_folder = function(path) if(path == nil) then return nil end local fh = io.popen("file "..path, "r") if(fh) then local res = fh:read("*a") local folder = string.match(res, ".-: directory$") fh:close() return (folder ~= nil) end return nil end end --------------------------------------------------------------------------------------- dirtools.make_folder = function(folderpath) local found = dirtools.is_folder(folderpath) if(found == nil) then local cmd = [[mkdir -f "]]..folderpath..[["]] if(ffi.os == "Windows") then cmd = [[mkdir "]]..folderpath..[["]] end local fh = io.popen(cmd, "r") if(fh) then local data = fh:read("*a") fh:close() log_info(data) return true else log_error("Cannot make folder: "..folderpath) return nil end else log_info("Folder already exists: "..folderpath) end end --------------------------------------------------------------------------------------- dirtools.get_relative_path = function( fullpath, parent_path ) local rpath = nil local str_st, str_end = string.find( fullpath, parent_path, 1, true ) if(str_st and str_end) then rpath = string.sub(fullpath, str_end+2, -1) end return rpath end --------------------------------------------------------------------------------------- dirtools.get_app_path = function( expected_root_folder ) local base_dir = "." if(ffi.os == "Windows") then local cmdh = io.popen("cd", "r") if(cmdh) then base_dir = cmdh:read("*a"); cmdh:close() end else local cmdh = io.popen("pwd", "r") if(cmdh) then base_dir = cmdh:read("*a"); cmdh:close() end end if(expected_root_folder) then local folder_name = expected_root_folder local last_folder, remain = string.match(base_dir, "(.-"..folder_name..")(.-)") last_folder = last_folder or "" remain = remain or "" remain = remain:gsub("%s+", "") if(string.len(last_folder) > 0) then base_dir = last_folder..sep else base_dir = "" end end -- log_info("Base Directory: "..base_dir) return base_dir end -- -------------------------------------------------------------------------------------- dirtools.get_folderslist = function(path, cache_update) -- Check path first. If its a drive on windows then no caching if(ffi.os == "Windows") then local colon = string.sub(path, 2,-1) if(colon == ":") then cache_update = true; path = path..sep end end if(list_folders_cache[path] and cache_update == nil) then return list_folders_cache[path] end local files = {} table.insert(files, 1, { name = ".." }) local res = "" -- Fill with temp file list of dir /b local fh = io.popen(string.format(allfolders_cmd, path), "r") if(fh) then res = fh:read("*a") fh:close() else log_error("dirtools.get_folderslist bad path: "..tostring(path)) return files end for f in string.gmatch(res, "(.-)\n") do local newfile = { name = ffi.string(f), folder = true } newfile.select = ffi.new("int[1]") newfile.select[0] = 0 table.insert(files, newfile) end list_folders_cache[path] = files return files end --------------------------------------------------------------------------------------- dirtools.get_dirlist = function(path, cache_update, extfilter) -- Check path first. If its a drive on windows then no caching if(ffi.os == "Windows") then local colon = string.match(path, "(.)$") if(colon == ":") then path = path..sep end end if(list_cache[path] and cache_update == nil) then return list_cache[path] end -- Get all the folders first local files = dirtools.get_folderslist(path, cache_update) -- Add the files to the list. local res = "" -- Fill with temp file list of dir /b local fh = io.popen(string.format(allfiles_cmd, path), "r") if(fh) then res = fh:read("*a") fh:close() if(#res == 0) then list_cache[path] = files return files end else log_error("dirtools.get_dirlist bad path: "..tostring(path)) return {} end for f in string.gmatch(res, "(.-)\n") do -- Only match with filter on end of filename local add_file = true if(extfilter) then add_file = string.match(f, extfilter.."$") end if(add_file) then local newfile = { name = ffi.string(f), folder = nil } newfile.select = ffi.new("int[1]") newfile.select[0] = 0 table.insert(files, newfile) end end list_cache[path] = files return files end --------------------------------------------------------------------------------------- dirtools.path_match = function(list, path) for i,v in ipairs(list) do if(dirtools.compare_paths(v.name, path)) then return true end end return false end --------------------------------------------------------------------------------------- dirtools.get_parent = function( path ) if(path == nil or path == "") then path = "." end local parentpath = dirtools.get_folder(path) return parentpath end ------------------------------------------------------------------------------------------------------------ local function windows_dir( folder ) local result = nil local f = io.popen("dir /AD /b \""..tostring(folder).."\"") if f then result = f:read("*a") else log_error("failed to read - "..tostring(folder)) end return result end ------------------------------------------------------------------------------------------------------------ local function unix_dir( folder ) local result = nil local f = io.popen("ls -d -A -G -N -1 * \""..tostring(folder).."\"") if f then result = f:read("*a") else log_error("failed to read - "..tostring(folder)) end return result end ------------------------------------------------------------------------------------------------------------ dirtools.getdirs = function ( folder ) local dirstr = "" if ffi.os == "HTML5" then elseif ffi.os == "Windows" then dirstr = windows_dir(folder) else dirstr = unix_dir(folder) end -- split string by line endings into a nice table local restbl = nil if(dirstr) then restbl = csplit(dirstr, "\n") end return restbl end --------------------------------------------------------------------------------------- dirtools.find_folder = function( start_path, parent_up, folder_name ) -- If starting in current application folder, convert it to a full path if(start_path == ".") then start_path = dirtools.get_app_path() end local list = dirtools.get_folderslist(start_path) if(dirtools.path_match(list, "sokol-luajit") == true) then return start_path end local current = start_path for i=1, parent_up do current = dirtools.get_parent(current) list = dirtools.get_folderslist(current) if(dirtools.path_match(list, "sokol-luajit") == true) then return current end end return nil end --------------------------------------------------------------------------------------- dirtools.fileparts = function( path ) return string.match(path, "(.-)([^\\/]-([^%.]+))$") end --------------------------------------------------------------------------------------- dirtools.change_folder = function( path, child ) local newpath = path..sep..child return newpath end --------------------------------------------------------------------------------------- -- By default the paths are added dirtools.init = function( base_path ) local path = dirtools.get_app_path(base_path) dirtools.add_default_paths(path) return dirtools end --------------------------------------------------------------------------------------- return dirtools ---------------------------------------------------------------------------------------