#!/usr/bin/env lua function show_help() print(string.format([[This program will help you keep your satellites fresh and up to date. Usage: %s sat = Check only one satellite dir = Check a dir full of satellites check = What file (sat) or directory (dir) to check save = What file (sat) or directory (dir) to save known versions into]], arg[0])) end -- Shamelessly stolen from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua function spairs(t) -- collect the keys local keys = {} for k in pairs(t) do keys[#keys+1] = k end table.sort(keys) -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end function call(command) --[[-- -- Runs a command, returns all output except trailing newline on success. --]]-- local pipe = io.popen(command) if pipe then local output = pipe:read("*all") if pipe:close() then if #output > 0 then return output:match("(.-)%s*$") end end end end function astrohelp(satellite, func) -- satellite_dir is an example of me being lazy and wanting to keep output clean in other functions. if satellite_dir then return call("astrohelp '" .. satellite_dir .. "/" .. satellite .. "' " .. func) else return call("astrohelp '" .. satellite .. "' " .. func) end end function download(url) return call("curl -Ls '" .. url .. "'") end function escape_regex(string) -- http://www.lua.org/manual/5.3/manual.html#6.4.1 local magic = "^$().[]*+-?" for i = 1, #magic do local char = magic:sub(i, i) string = string:gsub("%" .. char, "%%" .. char) end return string end function make_regex(filename, version) --[[-- -- Converts a string into regex, by escaping all special (magic) characters. -- Returns two regexes, meant to be used in a loop. (See code using this function to know what I mean) --]]-- local regex = escape_regex(filename:gsub(escape_regex(version), "{{version}}"):gsub("%%", "%%%%")) return regex:gsub("{{version}}", "(.-") .. ")", regex:gsub(".*{{version}}", "(.-)") end function get_available_versions(satellite) --[[-- -- Returns a set table containing all available versions on update_url --]]-- local version = astrohelp(satellite, "variable version") local update_url = astrohelp(satellite, "variable update_url") if not version or not update_url then return end io.stderr:write("\x1B[1K\r") io.stderr:write(satellite .. ": Downloading update file...") local update_file = download(update_url) if not update_file then io.stderr:write("\x1B[1K\r") print("WARNING: " .. satellite .. ": Failed to download " .. update_url) return end io.stderr:write("\x1B[1K\r") io.stderr:write(satellite .. ": Getting available versions...") -- Try getting variable update_names, get downloads otherwise. local update_names = astrohelp(satellite, "variable update_names") if not update_names then local tmp_update_names = astrohelp(satellite, "downloads") assert(tmp_update_names, "Could not get update names for " .. satellite) update_names = "" -- Take the basename of every download url for name in tmp_update_names:gmatch("[^\n]+") do update_names = update_names .. name:match("[^/]+$") .. "," end end local versions = {} local count = 0 for update_name in update_names:gmatch("[^,]+") do local loop_regex, end_regex = make_regex(update_name, version) for x in update_file:gmatch(loop_regex) do -- Sometimes, the match starts way too soon, and we get a very long "version" number. -- Here we make sure we really get the shortest possible match. local version repeat version = x x = x:match(loop_regex) until not x version = version:match(end_regex) versions[version] = true count = count + 1 end end io.stderr:write("\x1B[1K\r") if count < 1 then print("WARNING: " .. satellite .. ": Couldn't retrieve any versions") return elseif not versions[version] then print("WARNING: " .. satellite .. ": Current version isn't available") end return versions, update_url end function check_new_versions(satellite, version_file_path) --[[-- -- Check if new versions are available for a satellite. -- Logs new versions to log_file, which should be a file descriptor. -- Uses version_file_path to know where to store all known versions. --]]-- local versions, update_url = get_available_versions(satellite) if not versions then return end local version_file = io.open(version_file_path, "r+") local old_versions = nil if version_file then old_versions = {} for version in version_file:lines() do old_versions[version] = true end else print(satellite .. ": First check") version_file = io.open(version_file_path, "w") assert(version_file, "Failed to create " .. version_file_path) end local new_versions = {} if old_versions then for version, _ in spairs(versions) do if not old_versions[version] then table.insert(new_versions, version) end end else for version, _ in spairs(versions) do table.insert(new_versions, version) end end if #new_versions > 0 then print(satellite .. ": From '" .. update_url .. "'") for _, version in ipairs(new_versions) do version_file:write(version .. "\n") print(satellite .. ": " .. version) end end version_file:close() end if #arg < 3 then show_help() os.exit(1) end if arg[1] == "sat" then check_new_versions(arg[2], arg[3]) elseif arg[1] == "dir" then satellite_dir = arg[2] local version_dir = arg[3] assert(os.execute("mkdir -p " .. version_dir), "Failed to create " .. version_dir) local find = call("find " .. satellite_dir .. " -type f -name '*.sat' -printf '%P\n'") assert(find, "Failed to run find") local total = select(2, find:gsub("\n", "\n")) local cols = call("tput cols") local count = 1 for satellite in find:gmatch("(.-)\n") do if cols then cols = tonumber(cols) local countstr = tostring(count) .. "/" .. tostring(total) io.stderr:write("\r") for i = 1, cols - countstr:len() - 1 do io.stderr:write(" ") end io.stderr:write(countstr .. "\r") end local version_file = version_dir .. "/" .. satellite assert(os.execute("mkdir -p " .. version_file:match("^.+/")), "Failed to create dir for " .. satellite) check_new_versions(satellite, version_file) count = count + 1 end else show_help() os.exit(1) end