diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 05f229f..3fb4fa5 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -35,14 +35,54 @@ local function _(msgid) return gettext.dgettext("dtutils.file", msgid) end -dtutils_file.libdoc.functions["is_dir"] = { - Name = [[is_dir]], - Synopsis = [[check if a path is a directory]], +--[[ + local function to run a test command on windows and return a true if it succeeds + instead of returning true if it runs +]] + +local function _win_os_execute(cmd) + local result = nil + local p = io.popen(cmd) + local output = p:read("*a") + p:close() + if string.match(output, "true") then + result = true + else + result = false + end + return result +end + +--[[ + local function to determine if a path name is a windows executable +]] + +local function _is_windows_executable(path) + local result = false + if dtutils_file.test_file(path, "f") then + if string.match(path, ".exe$") or string.match(path, ".EXE$") or + string.match(path, ".com$") or string.match(path, ".COM$") or + string.match(path, ".bat$") or string.match(path, ".BAT$") or + string.match(path, ".cmd$") or string.match(path, ".CMD$") then + result = true + end + end + return result +end + +dtutils_file.libdoc.functions["test_file"] = { + Name = [[test_file]], + Synopsis = [[test a file to see what it is]], Usage = [[local df = require "lib/dtutils.file" - local result = df.is_dir(path) - path - string - the path to check]], - Description = [[is_dir checks a path to see if it is a directory]], + local result = df.test_file(path, test) + path - string - the path to check + test - one of d, e, f, x where + d - directory + e - exists + f - file + x - executable]], + Description = [[test_file checks a path to see if it is a directory]], Return_Value = [[result - boolean - true if path is a directory, nil if not]], Limitations = [[]], Example = [[]], @@ -52,126 +92,62 @@ dtutils_file.libdoc.functions["is_dir"] = { Copyright = [[]], } -function dtutils_file.is_dir(path) - local cmd = nil +function dtutils_file.test_file(path, test) + local cmd = "test -" + local engine = os.execute + local cmdstring = "" if dt.configuration.running_os == "windows" then - cmd = "if exist " .. ds.sanitize(path .. "\\*") - else - cmd = "test -d " .. ds.sanitize(path) + cmd = "if exist " + engine = _win_os_execute end - return os.execute(cmd) -end - -dtutils_file.libdoc.functions["is_file"] = { - Name = [[is_file]], - Synopsis = [[check if a path is a file]], - Usage = [[local df = require "lib/dtutils.file" - - local result = df.is_file(path) - path - string - the path to check]], - Description = [[is_file checks a path to see if it is a file]], - Return_Value = [[result - boolean - true if path is a file, nil if not]], - Limitations = [[]], - Example = [[]], - See_Also = [[]], - Reference = [[]], - License = [[]], - Copyright = [[]], -} - -function dtutils_file.is_file(path) - local cmd = nil - local result = false - - if dt.configuration.running_os == "windows" then - if not dtutils_file.is_dir(path) then - cmd = "if exist " .. ds.sanitize(path) .. " echo true" - local p = io.popen(cmd) - output = p:read("*a") - p:close() - if string.match(output, "true") then - result = true + if test == "d" then + -- test if directory + if dt.configuration.running_os == "windows" then + cmdstring = cmd .. dtutils_file.sanitize_filename(path .. "\\*") .. " echo true" + else + cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path) + end + elseif test == "e" then + -- test exists + if dt.configuration.running_os == "windows" then + cmdstring = cmd .. dtutils_file.sanitize_filename(path) .. " echo true" + else + cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path) + end + elseif test == "f" then + -- test if file + if dt.configuration.running_os == "windows" then + if not dtutils_file.test_file(path, "d") then -- make sure it's not a directory + cmdstring = cmd .. dtutils_file.sanitize_filename(path) .. " echo true" else - result = false + return false end else - return false + cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path) + end + elseif test == "x" then + -- test executable + if dt.configuration.running_os == "windows" then + return _is_windows_executable(path) + else + cmdstring = cmd .. test .. " " .. dtutils_file.sanitize_filename(path) end else - result = os.execute("test -f " .. ds.sanitize(path)) + dt.print_error("[test_file] unknown test " .. test) + return false end - return result + return engine(cmdstring) end -dtutils_file.libdoc.functions["is_executable"] = { - Name = [[is_executable]], - Synopsis = [[check if a path is a executable]], - Usage = [[local df = require "lib/dtutils.file" - - local result = df.is_executable(path) - path - string - the path to check]], - Description = [[is_executable checks a path to see if it is an executable]], - Return_Value = [[result - boolean - true if path is an executable, nil if not]], - Limitations = [[]], - Example = [[]], - See_Also = [[]], - Reference = [[]], - License = [[]], - Copyright = [[]], -} - -local function _is_windows_executable(path) - local result = nil - dt.print_log("checking " .. path) - - if string.match(path, ".exe$") or string.match(path, ".EXE$") or - string.match(path, ".com$") or string.match(path, ".COM$") or - string.match(path, ".bat$") or string.match(path, ".BAT$") or - string.match(path, ".cmd$") or string.match(path, ".CMD$") then - result = true - end - return result +local function _case_insensitive_pattern(pattern) + return pattern:gsub("(.)", function(letter) + return string.format("[%s$s]", letter:lower(), letter:upper()) + end) end -function dtutils_file.is_executable(path) - - local result = nil - - if dt.configuration.running_os == "windows" then - if _is_windows_executable(path) then - result = true - end - else - result = os.execute("test -x " .. ds.sanitize(path)) - end - return result -end - -dtutils_file.libdoc.functions["check_if_bin_exists"] = { - Name = [[check_if_bin_exists]], - Synopsis = [[check if an executable exists]], - Usage = [[local df = require "lib/dtutils.file" - - local result = df.check_if_bin_exists(bin) - bin - string - the binary to check for]], - Description = [[check_if_bin_exists checks to see if the specified binary exists. - check_if_bin_exists first checks to see if a preference for the binary has been - registered and uses that if found. The presence of the file is verified, then - quoted and returned. If no preference is specified and the operating system is - linux then the which command is used to check for a binary in the path. If found - that path is returned. If no binary is found, false is returned.]], - Return_Value = [[result - string - the sanitized path of the binary, false if not found]], - Limitations = [[]], - Example = [[]], - See_Also = [[]], - Reference = [[]], - License = [[]], - Copyright = [[]], -} - local function _search_for_bin_windows(bin) local result = false -- use where on path @@ -185,9 +161,11 @@ local function _search_for_bin_windows(bin) local output = p:read("*a") p:close() local lines = du.split(output, "\n") + local cibin = _case_insensitive_pattern(bin) for _,line in ipairs(lines) do - if string.match(line, bin) then - if dtutils_file.is_file(line) and dtutils_file.is_executable(line) then + if string.match(line, cibin) then + dt.print_log("found win search match " .. line) + if dtutils_file.test_file(line, "f") and dtutils_file.test_file(line, "x") then dtutils_file.set_executable_path_preference(bin, line) -- save it so we don't have to search again return line end @@ -204,7 +182,7 @@ local function _search_for_bin_nix(bin) p:close() if string.len(output) > 0 then local spath = dtutils_file.sanitize_filename(output:sub(1,-2)) - if dtutils_file.is_file(spath) and dtutils_file.is_executable(spath) then + if dtutils_file.test_file(spath, "f") and dtutils_file.test_file(spath, "x") then result = spath end end @@ -230,7 +208,7 @@ local function _search_for_bin_macos(bin) for _,line in ipairs(lines) do local spath = dtutils_file.sanitize_filename(line:sub(1, -2)) - if dtutils_file.is_executable(spath) then + if dtutils_file.test_file(spath, "x") then dtutils_file.set_executable_path_preference(bin, spath) result = spath end @@ -245,6 +223,9 @@ local function _search_for_bin(bin) if dt.configuration.running_os == "windows" then result = _search_for_bin_windows(bin) + if result then + result = dtutils_file.sanitize_filename(result) + end elseif dt.configuration.running_os == "macos" then result = _search_for_bin_macos(bin) else @@ -279,13 +260,13 @@ local function _check_path_for_bin(bin) else path = dtutils_file.get_executable_path_preference(bin) -- reset path preference is the returned preference is a directory - if dtutils_file.is_dir(path) then + if dtutils_file.test_file(path, "d") then dtutils_file.set_executable_path_preference(bin, "") path = nil end end - if path and dtutils_file.is_dir(path) then + if path and dtutils_file.test_file(path, "d") then path = nil end @@ -294,7 +275,7 @@ local function _check_path_for_bin(bin) end if path and not result then - if dtutils_file.is_executable(path) then + if dtutils_file.test_file(path, "x") then result = dtutils_file.sanitize_filename(path) end end @@ -302,15 +283,69 @@ local function _check_path_for_bin(bin) return result end +local function _old_check_if_bin_exists(bin) -- only run on windows if preference checked + local result = false + local path = nil + + if string.match(bin, "\\") then + path = bin + else + path = dtutils_file.get_executable_path_preference(bin) + end + + if string.len(path) > 0 then + if dtutils_file.check_if_file_exists(path) then + if (string.match(path, ".exe$") or string.match(path, ".EXE$")) then + result = dtutils_file.sanitize_filename(path) + end + end + end + return result +end + +dtutils_file.libdoc.functions["check_if_bin_exists"] = { + Name = [[check_if_bin_exists]], + Synopsis = [[check if an executable exists]], + Usage = [[local df = require "lib/dtutils.file" + + local result = df.check_if_bin_exists(bin) + bin - string - the binary to check for]], + Description = [[check_if_bin_exists checks to see if the specified binary exists. + check_if_bin_exists first checks to see if a preference for the binary has been + registered and uses that if found, after it's verified to be an executable and + exist. If no preference exissts, the user's path is checked for the executable. + If the executable is not found in the users path, then a search of the operating + system is conducted to see if the executable can be found. + + If an executalble is found, it's verified to exist and be an executable. Once + the executable is verified, the path is saved as a preference to speed up + subsequent checks. The executable path is sanitized and returned. + + If no executable is found, false is returned.]], + Return_Value = [[result - string - the sanitized path of the binary, false if not found]], + Limitations = [[If more than one executable that satisfies the search results is found, the + wrong one may be returned. If the wrong value is returned, the user can still specify the + correct execuable using tools/executable_manager.]], + Example = [[]], + See_Also = [[executable_manager]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + function dtutils_file.check_if_bin_exists(bin) local result = false - result = _check_path_for_bin(bin) + if dt.configuration.running_os == "windows" and dt.preferences.read("dtutils.file", "use_old_check_if_bin_exists", "bool") then + result = _old_check_if_bin_exists(bin) + else - if not result then - result = _search_for_bin(bin) + result = _check_path_for_bin(bin) + + if not result then + result = _search_for_bin(bin) + end end - return result end @@ -856,5 +891,21 @@ function dtutils_file.rmdir(path) return dsys.external_command(rm_cmd.." "..path) end +--[[ + The new check_if_bin_exists() does multiple calls to the operating system to check + if the file exists and is an executable. On windows, each call to the operating system + causes a window to open in order to run the command, then the window closes when the + command exits. If the user gets annoyed by the "flickering windows", then they can + enable this preference to use the old check_if_bin_exists() that relys on the + executable path preferences and doesn't do as many checks. +]] + +if dt.configuration.running_os == "windows" then + dt.preferences.register("dtutils.file", "use_old_check_if_bin_exists", "bool", + "lua scripts use old check_if_bin_exists()", + "lessen flickering windows effect when scripts run", + false) +end + return dtutils_file