diff options
-rw-r--r-- | src/lpm.lua | 286 |
1 files changed, 148 insertions, 138 deletions
diff --git a/src/lpm.lua b/src/lpm.lua index 0f2c7c2..35e1de7 100644 --- a/src/lpm.lua +++ b/src/lpm.lua @@ -475,6 +475,8 @@ end local LATEST_MOD_VERSION = "3.0.0" local EXECUTABLE_EXTENSION = PLATFORM == "windows" and ".exe" or "" local HOME, USERDIR, CACHEDIR, JSON, TABLE, HEADER, RAW, VERBOSE, FILTRATION, MOD_VERSION, QUIET, FORCE, REINSTALL, CONFIG, NO_COLOR, AUTO_PULL_REMOTES, ARCH, ASSUME_YES, NO_INSTALL_OPTIONAL, TMPDIR, DATADIR, BINARY, POST, PROGRESS, SYMLINK, REPOSITORY, EPHEMERAL, MASK, settings, repositories, lite_xls, system_bottle, progress_bar_label, write_progress_bar +local SHOULD_COLOR = os.getenv("TERM") and os.getenv("TERM") ~= "dumb" and not os.getenv("NO_COLOR") +local Addon, Repository, LiteXL, Bottle, lpm, log = {}, {}, {}, {}, {}, {} local function engage_locks(func, err, warn) if not system.stat(CACHEDIR) then common.mkdirp(CACHEDIR) end @@ -483,8 +485,6 @@ local function engage_locks(func, err, warn) return system.flock(lockfile, func, err, warn) end -local Addon, Repository, LiteXL, Bottle = {}, {}, {}, {} - local colors = { red = 31, green = 32, @@ -492,35 +492,34 @@ local colors = { blue = 34, cyan = 36 } -local SHOULD_COLOR = os.getenv("TERM") and os.getenv("TERM") ~= "dumb" and not os.getenv("NO_COLOR") local function colorize(text, color) if not SHOULD_COLOR or not TTY or NO_COLOR or not color then return text end return "\x1B[" .. colors[color] .. "m" .. text .. "\x1B[0m" end local actions, warnings = {}, {} -local function log_action(message, color) +function log.action(message, color) if JSON then table.insert(actions, message) end if not QUIET then io.stderr:write(colorize(message .. "\n", color)) io.stderr:flush() end end -local function log_warning(message) +function log.warning(message) if JSON then table.insert(warnings, message) end if not QUIET then io.stderr:write(colorize("warning: " .. message .. "\n", "yellow")) io.stderr:flush() end end -local function fatal_warning(message) - if not FORCE then error(message .. "; use --force to override") else log_warning(message) end +function log.fatal_warning(message) + if not FORCE then error(message .. "; use --force to override") else log.warning(message) end end -local function log_progress_action(message) +function log.progress_action(message) if write_progress_bar then progress_bar_label = message else - log_action(message) + log.action(message) end end local function prompt(message) @@ -535,28 +534,6 @@ local function prompt(message) return not response:find("%S") or response:find("^%s*[yY]%s*$") end -local status = 0 -local function error_handler(err) - local s, e - if err then s, e = err:find("%:%d+") end - local message = e and err:sub(e + 3) or err - if JSON then - if VERBOSE then - io.stderr:write(json.encode({ error = err, actions = actions, warnings = warnings, traceback = debug.traceback(nil, 2) }) .. "\n") - else - io.stderr:write(json.encode({ error = message or err, actions = actions, warnings = warnings }) .. "\n") - end - else - if err then io.stderr:write(colorize((not VERBOSE and message or err) .. "\n", "red")) end - if VERBOSE then io.stderr:write(debug.traceback(nil, 2) .. "\n") end - end - io.stderr:flush() - status = -1 -end -local function lock_warning() - log_warning("waiting for lpm global lock to be released (only one instance of lpm can be run at once)") -end - function common.get(source, options) options = options or {} @@ -564,7 +541,7 @@ function common.get(source, options) if not source then error("requires url") end if (depth or 0) > 10 then error("too many redirects") end local _, _, protocol, hostname, port, rest = source:find("^(https?)://([^:/?]+):?(%d*)(.*)$") - log_progress_action("Downloading " .. source:sub(1, 100) .. "...") + log.progress_action("Downloading " .. source:sub(1, 100) .. "...") if not protocol then error("malfomed url " .. source) end if not port or port == "" then port = protocol == "https" and 443 or 80 end if not rest or rest == "" then rest = "/" end @@ -584,7 +561,7 @@ function common.get(source, options) if headers.location then return common.get(headers.location, common.merge(options, { depth = (depth or 0) + 1 })) end if checksum ~= "SKIP" and system.hash(cache_path .. ".part", "file") ~= checksum then common.rmrf(cache_path .. ".part") - fatal_warning("checksum doesn't match for " .. source) + log.fatal_warning("checksum doesn't match for " .. source) end common.rename(cache_path .. ".part", cache_path) end @@ -702,8 +679,8 @@ function Addon:unstub() local addon = Addon.new(repo, remote_entry) -- merge in attribtues that are probably more accurate than the stub - if addon.version ~= self.version then log_warning(self.id .. " stub on " .. self.repository:url() .. " has differing version from remote (" .. self.version .. " vs " .. addon.version .. "); may lead to install being inconsistent") end - -- if addon.mod_version ~= self.mod_version then log_warning(self.id .. " stub on " .. self.repository:url() .. " has differing mod_version from remote (" .. self.mod_version .. " vs " .. addon.mod_version .. ")") end + if addon.version ~= self.version then log.warning(self.id .. " stub on " .. self.repository:url() .. " has differing version from remote (" .. self.version .. " vs " .. addon.version .. "); may lead to install being inconsistent") end + -- if addon.mod_version ~= self.mod_version then log.warning(self.id .. " stub on " .. self.repository:url() .. " has differing mod_version from remote (" .. self.mod_version .. " vs " .. addon.mod_version .. ")") end for k,v in pairs(addon) do self[k] = v end end) if not status then self.inaccessible = err end @@ -790,7 +767,7 @@ end function Addon:install(bottle, installing) - if MASK[self.id] then if not installing[self.id] then log_warning("won't install masked addon " .. self.id) end installing[self.id] = true return end + if MASK[self.id] then if not installing[self.id] then log.warning("won't install masked addon " .. self.id) end installing[self.id] = true return end if self:is_installed(bottle) and not REINSTALL then error("addon " .. self.id .. " is already installed") return end if self:is_stub() then self:unstub() end if self.inaccessible then error("addon " .. self.id .. " is inaccessible: " .. self.inaccessible) end @@ -811,7 +788,7 @@ function Addon:install(bottle, installing) if not v.optional then error("can't find dependency " .. addon .. (v.version and (":" .. v.version) or "")) else - log_warning("can't find optional dependency " .. addon .. (v.version and (":" .. v.version) or "")) + log.warning("can't find optional dependency " .. addon .. (v.version and (":" .. v.version) or "")) end end end @@ -828,32 +805,32 @@ function Addon:install(bottle, installing) end if self.type == "meta" then - log_action("Installed metapackage " .. self.id .. ".", "green") + log.action("Installed metapackage " .. self.id .. ".", "green") return end common.mkdirp(common.dirname(temporary_install_path)) if self:is_upgradable(bottle) then - log_action("Upgrading " .. self.organization .. " " .. self.type .. " " .. self.id .. ".", "green") + log.action("Upgrading " .. self.organization .. " " .. self.type .. " " .. self.id .. ".", "green") common.rmrf(install_path) else - log_action("Installing " .. self.organization .. " " .. self.type .. " " .. self.id .. ".", "green") + log.action("Installing " .. self.organization .. " " .. self.type .. " " .. self.id .. ".", "green") end if self.organization == "complex" and self.path and common.stat(self.local_path).type ~= "dir" then common.mkdirp(install_path) end if self.url then -- remote simple addon local path = temporary_install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") common.get(self.url, { target = path, checksum = self.checksum, callback = write_progress_bar }) - if VERBOSE then log_action("Downloaded file " .. self.url .. " to " .. path) end + if VERBOSE then log.action("Downloaded file " .. self.url .. " to " .. path) end else -- local addon that has a local path local temporary_path = temporary_install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") if self.organization == 'complex' and self.path and common.stat(self.local_path).type ~= "dir" then common.mkdirp(temporary_install_path) end if self.path then local path = install_path .. (self.organization == 'complex' and self.path and common.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") if SYMLINK then - if VERBOSE then log_action("Symlinking " .. self.local_path .. " to " .. path .. ".") end + if VERBOSE then log.action("Symlinking " .. self.local_path .. " to " .. path .. ".") end system.symlink(self.local_path, temporary_path) else - if VERBOSE then log_action("Copying " .. self.local_path .. " to " .. path .. ".") end + if VERBOSE then log.action("Copying " .. self.local_path .. " to " .. path .. ".") end common.copy(self.local_path, temporary_path) end end @@ -878,10 +855,10 @@ function Addon:install(bottle, installing) if not system.stat(temporary_path) then common.mkdirp(common.dirname(temporary_path)) if SYMLINK and self.repository:is_local() and system.stat(local_path) then - log_action("Symlinking " .. local_path .. " to " .. target_path .. ".") + log.action("Symlinking " .. local_path .. " to " .. target_path .. ".") system.symlink(local_path, temporary_path) elseif SYMLINK and self.repository:is_local() and system.stat(stripped_local_path) then - log_action("Symlinking " .. stripped_local_path .. " to " .. target_path .. ".") + log.action("Symlinking " .. stripped_local_path .. " to " .. target_path .. ".") system.symlink(stripped_local_path, temporary_path) else common.get(file.url, { target = temporary_path, checksum = file.checksum, callback = write_progress_bar }) @@ -889,7 +866,7 @@ function Addon:install(bottle, installing) local is_archive = basename:find("%.zip$") or basename:find("%.tar%.gz$") or basename:find("%.tgz$") local target = temporary_path if is_archive or basename:find("%.gz$") then - if VERBOSE then log_action("Extracting file " .. basename .. " in " .. install_path .. "...") end + if VERBOSE then log.action("Extracting file " .. basename .. " in " .. install_path .. "...") end target = temporary_install_path .. (not is_archive and (PATHSEP .. basename:gsub(".gz$", "")) or "") system.extract(temporary_path, target) os.remove(temporary_path) @@ -902,7 +879,7 @@ function Addon:install(bottle, installing) if path:find(PATHSEP .. "%.%.") then error("invalid chmod_executable value " .. executable) end local stat = system.stat(path) if not stat then error("can't find executable to chmod_executable " .. path) end - if VERBOSE then log_action("Chmodding file " .. executable .. " to be executable.") end + if VERBOSE then log.action("Chmodding file " .. executable .. " to be executable.") end system.chmod(path, stat.mode | 73) end end @@ -972,7 +949,7 @@ end function Addon:uninstall(bottle, uninstalling) - if MASK[self.id] then if not uninstalling[self.id] then log_warning("won't uninstall masked addon " .. self.id) end uninstalling[self.id] = true return end + if MASK[self.id] then if not uninstalling[self.id] then log.warning("won't uninstall masked addon " .. self.id) end uninstalling[self.id] = true return end local install_path = self:get_install_path(bottle) if self:is_core(bottle) then error("can't uninstall " .. self.id .. "; is a core addon") end local orphans = common.sort(common.grep(self:get_orphaned_dependencies(bottle), function(e) return not uninstalling or not uninstalling[e.id] end), function(a, b) return a.id < b.id end) @@ -982,9 +959,9 @@ function Addon:uninstall(bottle, uninstalling) end common.each(orphans, function(e) e:uninstall(bottle, common.merge(uninstalling or {}, { [self.id] = true })) end) if self.type == "meta" then - log_action("Uninstalling meta " .. self.id .. ".", "green") + log.action("Uninstalling meta " .. self.id .. ".", "green") else - log_action("Uninstalling " .. self.type .. " located at " .. install_path, "green") + log.action("Uninstalling " .. self.type .. " located at " .. install_path, "green") end local incompatible_addons = common.grep(bottle:installed_addons(), function(p) return p:depends_on(self) and (not uninstalling or not uninstalling[p.id]) end) local should_uninstall = #incompatible_addons == 0 or uninstalling @@ -1069,7 +1046,7 @@ function Repository:parse_manifest(repo_id) if system.stat(self.local_path) then self.manifest_path = self.local_path .. PATHSEP .. "manifest.json" if not system.stat(self.manifest_path) then - log_warning("Can't find manifest.json for " .. self:url() .. "; automatically generating manifest.") + log.warning("Can't find manifest.json for " .. self:url() .. "; automatically generating manifest.") self:generate_manifest(repo_id) end local status, err = pcall(function() @@ -1191,7 +1168,7 @@ function Repository:fetch() path = self.repo_path .. PATHSEP .. "master" common.rmrf(temporary_path) common.mkdirp(temporary_path) - log_progress_action("Fetching " .. self.remote .. "...") + log.progress_action("Fetching " .. self.remote .. "...") system.init(temporary_path, self.remote) self.branch = system.fetch(temporary_path, write_progress_bar) if not self.branch then error("Can't find remote branch for " .. self.remote) end @@ -1206,7 +1183,7 @@ function Repository:fetch() system.init(temporary_path, self.remote) end if not exists or self.branch then - log_progress_action("Fetching " .. self.remote .. ":" .. (self.commit or self.branch) .. "...") + log.progress_action("Fetching " .. self.remote .. ":" .. (self.commit or self.branch) .. "...") if self.commit then system.fetch(temporary_path or path, write_progress_bar, self.commit) elseif self.branch then @@ -1250,7 +1227,7 @@ end function Repository:update(pull_remotes) local manifest, remotes = self:parse_manifest() if self.branch then - log_progress_action("Updating " .. self:url() .. "...") + log.progress_action("Updating " .. self:url() .. "...") local status, err = pcall(system.fetch, self.local_path, write_progress_bar, "+refs/heads/" .. self.branch .. ":refs/remotes/origin/" .. self.branch) if not status then -- see https://github.com/lite-xl/lite-xl-plugin-manager/issues/85 if not err:find("object not found %- no match for id") then error(err, 0) end @@ -1306,7 +1283,7 @@ function LiteXL:is_compatible(addon) return not addon.mod_version or compatible_ function LiteXL:is_installed() return system.stat(self.local_path) ~= nil end function LiteXL:install() - if self:is_installed() then log_warning("lite-xl " .. self.version .. " already installed") return end + if self:is_installed() then log.warning("lite-xl " .. self.version .. " already installed") return end common.mkdirp(self.local_path) if system_bottle.lite_xl == self then -- system lite-xl. We have to copy it because we can't really set the user directory. local executable, datadir = common.path("lite-xl" .. EXECUTABLE_EXTENSION) @@ -1331,11 +1308,11 @@ function LiteXL:install() local basename = common.basename(file.url) local archive = basename:find("%.zip$") or basename:find("%.tar%.gz$") local path = self.local_path .. PATHSEP .. (archive and basename or "lite-xl") - log_action("Downloading file " .. file.url .. "...") + log.action("Downloading file " .. file.url .. "...") common.get(file.url, { target = path, checksum = file.checksum, callback = write_progress_bar }) - log_action("Downloaded file " .. file.url .. " to " .. path) + log.action("Downloaded file " .. file.url .. " to " .. path) if archive then - log_action("Extracting file " .. basename .. " in " .. self.local_path) + log.action("Extracting file " .. basename .. " in " .. self.local_path) system.extract(path, self.local_path) end end @@ -1424,7 +1401,7 @@ function Bottle:run(args) local line = path .. (#args > 0 and " " or "") .. table.concat(common.map(args, function(arg) return "'" .. arg:gsub("'", "'\"'\"'"):gsub("\\", "\\\\") .. "'" end), " ") - if VERBOSE then log_action("Running " .. line) end + if VERBOSE then log.action("Running " .. line) end return os.execute(line) end @@ -1567,35 +1544,33 @@ local function get_repository(url) return nil end - -local function lpm_settings_save() +function lpm.settings_save() common.write(CACHEDIR .. PATHSEP .. "settings.json", json.encode(settings)) end -local function lpm_repo_save() +function lpm.repo_save() settings.repositories = common.map(repositories, function(r) return r:url() end) - lpm_settings_save() + lpm.settings_save() end - local DEFAULT_REPOS -local function lpm_repo_init(repos) +function lpm.repo_init(repos) DEFAULT_REPOS = { Repository.url(DEFAULT_REPO_URL) } common.mkdirp(CACHEDIR) if not system.stat(CACHEDIR .. PATHSEP .. "settings.json") then for i, repository in ipairs(repos or DEFAULT_REPOS) do table.insert(repositories, repository:add(true)) end - lpm_repo_save() + lpm.repo_save() end end -local function lpm_repo_add(...) +function lpm.repo_add(...) for i, url in ipairs({ ... }) do local idx, repo = get_repository(url) if repo then -- if we're alreayd a repo, put this at the head of the resolution list @@ -1606,22 +1581,22 @@ local function lpm_repo_add(...) table.insert(repositories, 1, repo) repo:update() end - lpm_repo_save() + lpm.repo_save() end -local function lpm_repo_rm(...) +function lpm.repo_rm(...) for i, url in ipairs({ ... }) do local idx, repo = get_repository(url) if not repo then error("cannot find repository " .. url) end table.remove(repositories, idx) repo:remove() end - lpm_repo_save() + lpm.repo_save() end -local function lpm_repo_update(...) +function lpm.repo_update(...) local t = { ... } if #t == 0 then table.insert(t, false) end for i, url in ipairs(t) do @@ -1638,12 +1613,12 @@ local function get_lite_xl(version) return common.first(common.concat(lite_xls, common.flat_map(repositories, function(e) return e.lite_xls end)), function(lite_xl) return lite_xl.version == version end) end -local function lpm_lite_xl_save() +function lpm.lite_xl_save() settings.lite_xls = common.map(common.grep(lite_xls, function(l) return l:is_local() and not l:is_system() end), function(l) return { version = l.version, mod_version = l.mod_version, path = l.path, binary_path = l.binary_path, datadir_path = l.datadir_path } end) - lpm_settings_save() + lpm.settings_save() end -local function lpm_lite_xl_add(version, path) +function lpm.lite_xl_add(version, path) if not version then error("requires a version") end if not version:find("^%d") then error("versions must begin numerically (i.e. 2.1.1-debug)") end if common.first(lite_xls, function(lite_xl) return lite_xl.version == version end) then error(version .. " lite-xl already exists") end @@ -1655,28 +1630,28 @@ local function lpm_lite_xl_add(version, path) local path_stat = system.stat(path:gsub(PATHSEP .. "$", "")) if not path_stat then error("can't find lite-xl path " .. path) end table.insert(lite_xls, LiteXL.new(nil, { version = version, binary_path = { [ARCH[1]] = binary_stat.abs_path }, datadir_path = data_stat.abs_path, path = path_stat.abs_path, mod_version = MOD_VERSION or LATEST_MOD_VERSION })) - lpm_lite_xl_save() + lpm.lite_xl_save() end -local function lpm_lite_xl_rm(version) +function lpm.lite_xl_rm(version) if not version then error("requires a version") end local lite_xl = get_lite_xl(version) or error("can't find lite_xl version " .. version) lite_xls = common.grep(lite_xls, function(l) return l ~= lite_xl end) - lpm_lite_xl_save() + lpm.lite_xl_save() end -local function lpm_lite_xl_install(version) +function lpm.lite_xl_install(version) if not version then error("requires a version") end (get_lite_xl(version) or error("can't find lite-xl version " .. version)):install() end -local function lpm_lite_xl_switch(version, target) +function lpm.lite_xl_switch(version, target) if not version then error("requires a version") end target = target or common.path("lite-xl" .. EXECUTABLE_EXTENSION) if not target then error("can't find installed lite-xl. please provide a target to install the symlink explicitly as a second argument") end local lite_xl = get_lite_xl(version) or error("can't find lite-xl version " .. version) - if not lite_xl:is_installed() then log_action("Installing lite-xl " .. lite_xl.version) lite_xl:install() end + if not lite_xl:is_installed() then log.action("Installing lite-xl " .. lite_xl.version) lite_xl:install() end local stat = system.stat(target) if stat and stat.symlink then os.remove(target) end system.symlink(lite_xl:get_binary_path(), target) @@ -1687,12 +1662,12 @@ local function lpm_lite_xl_switch(version, target) end -local function lpm_lite_xl_uninstall(version) +function lpm.lite_xl_uninstall(version) (get_lite_xl(version) or error("can't find lite-xl version " .. version)):uninstall() end -local function lpm_lite_xl_list() +function lpm.lite_xl_list() local result = { ["lite-xls"] = { } } local max_version = 0 for i,lite_xl in ipairs(lite_xls) do @@ -1751,7 +1726,7 @@ local function is_argument_repo(arg) return arg:find("^http") or arg:find("[\\/]") or arg == "." end -local function lpm_lite_xl_run(version, ...) +function lpm.lite_xl_run(version, ...) if not version then error("requires a version or arguments") end local arguments = { ... } if not version:find("^%d+") and version ~= "system" then @@ -1795,7 +1770,7 @@ local function lpm_lite_xl_run(version, ...) end -local function lpm_install(type, ...) +function lpm.install(type, ...) local repo_only = nil local to_install = {} local to_explicitly_install = {} @@ -1804,7 +1779,7 @@ local function lpm_install(type, ...) local id, version = (s and identifier:sub(1, s-1) or identifier), (s and identifier:sub(s+1) or nil) if not id then error('unrecognized identifier ' .. identifier) end if id == "lite-xl" then - lpm_lite_xl_install(version) + lpm.lite_xl_install(version) else if is_argument_repo(identifier) then table.insert(repositories, 1, Repository.url(identifier):add(AUTO_PULL_REMOTES)) @@ -1816,7 +1791,7 @@ local function lpm_install(type, ...) local addons = common.grep(potential_addons, function(e) return e:is_installable(system_bottle) and (not e:is_installed(system_bottle) or REINSTALL) end) if #addons == 0 and #potential_addons == 0 then error("can't find " .. (type or "addon") .. " " .. id .. " mod-version: " .. (system_bottle.lite_xl.mod_version or 'any')) end if #addons == 0 then - log_warning((potential_addons[1].type or "addon") .. " " .. id .. " already installed") + log.warning((potential_addons[1].type or "addon") .. " " .. id .. " already installed") if not common.first(settings.installed, id) then table.insert(to_explicitly_install, id) end else for j,v in ipairs(addons) do @@ -1835,7 +1810,7 @@ local function lpm_install(type, ...) end end) settings.installed = common.concat(settings.installed, to_explicitly_install) - lpm_settings_save() + lpm.settings_save() end local function get_table(headers, rows) @@ -1932,7 +1907,7 @@ local function print_addon_info(type, addons, filters) end -local function lpm_unstub(type, ...) +function lpm.unstub(type, ...) local addons = {} for i, identifier in ipairs({ ... }) do if not identifier then error('unrecognized identifier ' .. identifier) end @@ -1944,7 +1919,7 @@ local function lpm_unstub(type, ...) addons = common.grep(potential_addons, function(e) return e:is_stub() end) if #addons == 0 and #potential_addons == 0 then error("can't find " .. (type or "addon") .. " " .. identifier .. " mod-version: " .. (system_bottle.lite_xl.mod_version or 'any')) end if #addons == 0 then - log_warning((potential_addons[1].type or "addon") .. " " .. identifier .. " already unstubbed") + log.warning((potential_addons[1].type or "addon") .. " " .. identifier .. " already unstubbed") end end end @@ -1953,7 +1928,7 @@ local function lpm_unstub(type, ...) end -local function lpm_addon_uninstall(type, ...) +function lpm.addon_uninstall(type, ...) for i, id in ipairs({ ... }) do local addons = { system_bottle:get_addon(id, nil, { type = type }) } if #addons == 0 then error("can't find addon " .. id) end @@ -1964,12 +1939,12 @@ local function lpm_addon_uninstall(type, ...) settings.installed = common.grep(settings.installed, function(e) return e ~= addon.id end) end end - lpm_settings_save() + lpm.settings_save() end -local function lpm_addon_reinstall(type, ...) for i, id in ipairs({ ... }) do pcall(lpm_addon_uninstall, type, id) end lpm_install(type, ...) end +function lpm.addon_reinstall(type, ...) for i, id in ipairs({ ... }) do pcall(lpm.addon_uninstall, type, id) end lpm.install(type, ...) end -local function lpm_repo_list() +function lpm.repo_list() if JSON then io.stdout:write(json.encode({ repositories = common.map(repositories, function(repo) return { remote = repo.remote, commit = repo.commit, branch = repo.branch, path = repo.local_path, remotes = common.map(repo.remotes or {}, function(r) return r:url() end) } end) }) .. "\n") else @@ -1983,23 +1958,23 @@ local function lpm_repo_list() end end -local function lpm_addon_list(type, id, filters) +function lpm.addon_list(type, id, filters) print_addon_info(type, common.grep(system_bottle:all_addons(), function(p) return (not type or p.type == type) and (not id or p.id:find(id)) end), filters) end -local function lpm_describe() +function lpm.describe() local repo_urls = common.grep(common.map(repositories, function(e) return e:url() end), function(url) return #common.grep(DEFAULT_REPOS, function(r) return r:url() == url end) == 0 end) print("lpm run " .. common.join(" ", { system_bottle.lite_xl.version, table.unpack(repo_urls) }) .. " " .. common.join(" ", common.map(system_bottle:installed_addons(), function(p) return p.id .. ":" .. p.version end))) end -local function lpm_addon_upgrade() +function lpm.addon_upgrade() for i,addon in ipairs(system_bottle:installed_addons()) do local upgrade = common.sort({ system_bottle:get_addon(addon.id, ">" .. addon.version) }, function(a, b) return compare_version(b.version, a.version) end)[1] if upgrade then upgrade:install(system_bottle) end end end -local function lpm_self_upgrade(release) +function lpm.self_upgrade(release) if not DEFAULT_RELEASE_URL or #DEFAULT_RELEASE_URL == 0 then error("self-upgrade has been disabled on lpm version " .. VERSION .. "; please upgrade it however you installed it") end local path = ARGV[1]:find(PATHSEP) and system.stat(ARGV[1]) and ARGV[1] or common.path(ARGV[1]) if not path then error("can't find path to lpm") end @@ -2021,19 +1996,19 @@ local function lpm_self_upgrade(release) if PLATFORM ~= "windows" then -- because we can't delete the running executbale on windows common.rmrf(old_temporary_file) end - log_action("Upgraded lpm to " .. release .. ".") + log.action("Upgraded lpm to " .. release .. ".") else - log_warning("aborting upgrade; remote executable is identical to current") + log.warning("aborting upgrade; remote executable is identical to current") common.rmrf(new_temporary_file) end end -local function lpm_bottle_purge() +function lpm.bottle_purge() common.rmrf(CACHEDIR .. PATHSEP .. "bottles") end -local function lpm_purge() - log_action("Purged " .. CACHEDIR .. ".", "green") +function lpm.purge() + log.action("Purged " .. CACHEDIR .. ".", "green") common.rmrf(CACHEDIR) end @@ -2073,36 +2048,36 @@ end local function run_command(ARGS) if not ARGS[2]:find("%S") then return elseif ARGS[2] == "init" then return - elseif ARGS[2] == "repo" and ARGV[3] == "add" then lpm_repo_add(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "repo" and ARGS[3] == "rm" then lpm_repo_rm(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "add" then lpm_repo_add(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "rm" then lpm_repo_rm(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "update" then lpm_repo_update(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "repo" and ARGS[3] == "update" then lpm_repo_update(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "repo" and (#ARGS == 2 or ARGS[3] == "list") then return lpm_repo_list() - elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and ARGS[3] == "install" then lpm_install(ARGS[2], table.unpack(common.slice(ARGS, 4))) - elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and ARGS[3] == "uninstall" then lpm_addon_uninstall(ARGS[2], table.unpack(common.slice(ARGS, 4))) - elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and ARGS[3] == "reinstall" then lpm_addon_reinstall(ARGS[2], table.unpack(common.slice(ARGS, 4))) - elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and (#ARGS == 2 or ARGS[3] == "list") then return lpm_addon_list(ARGS[2], ARGS[4], ARGS) - elseif ARGS[2] == "upgrade" then return lpm_addon_upgrade(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "install" then lpm_install(nil, table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "unstub" then return lpm_unstub(nil, table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "uninstall" then lpm_addon_uninstall(nil, table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "reinstall" then lpm_addon_reinstall(nil, table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "describe" then lpm_describe(nil, table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "list" then return lpm_addon_list(nil, ARGS[3], ARGS) - elseif ARGS[2] == "lite-xl" and (#ARGS == 2 or ARGS[3] == "list") then return lpm_lite_xl_list(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "lite-xl" and ARGS[3] == "uninstall" then return lpm_lite_xl_uninstall(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "lite-xl" and ARGS[3] == "install" then return lpm_lite_xl_install(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "lite-xl" and ARGS[3] == "switch" then return lpm_lite_xl_switch(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "lite-xl" and ARGS[3] == "run" then return lpm_lite_xl_run(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "lite-xl" and ARGS[3] == "add" then return lpm_lite_xl_add(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "lite-xl" and ARGS[3] == "rm" then return lpm_lite_xl_rm(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "repo" and ARGV[3] == "add" then lpm.repo_add(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "repo" and ARGS[3] == "rm" then lpm.repo_rm(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "add" then lpm.repo_add(table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "rm" then lpm.repo_rm(table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "update" then lpm.repo_update(table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "repo" and ARGS[3] == "update" then lpm.repo_update(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "repo" and (#ARGS == 2 or ARGS[3] == "list") then return lpm.repo_list() + elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and ARGS[3] == "install" then lpm.install(ARGS[2], table.unpack(common.slice(ARGS, 4))) + elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and ARGS[3] == "uninstall" then lpm.addon_uninstall(ARGS[2], table.unpack(common.slice(ARGS, 4))) + elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and ARGS[3] == "reinstall" then lpm.addon_reinstall(ARGS[2], table.unpack(common.slice(ARGS, 4))) + elseif (ARGS[2] == "plugin" or ARGS[2] == "color" or ARGS[2] == "library" or ARGS[2] == "font") and (#ARGS == 2 or ARGS[3] == "list") then return lpm.addon_list(ARGS[2], ARGS[4], ARGS) + elseif ARGS[2] == "upgrade" then return lpm.addon_upgrade(table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "install" then lpm.install(nil, table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "unstub" then return lpm.unstub(nil, table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "uninstall" then lpm.addon_uninstall(nil, table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "reinstall" then lpm.addon_reinstall(nil, table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "describe" then lpm.describe(nil, table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "list" then return lpm.addon_list(nil, ARGS[3], ARGS) + elseif ARGS[2] == "lite-xl" and (#ARGS == 2 or ARGS[3] == "list") then return lpm.lite_xl_list(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "lite-xl" and ARGS[3] == "uninstall" then return lpm.lite_xl_uninstall(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "lite-xl" and ARGS[3] == "install" then return lpm.lite_xl_install(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "lite-xl" and ARGS[3] == "switch" then return lpm.lite_xl_switch(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "lite-xl" and ARGS[3] == "run" then return lpm.lite_xl_run(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "lite-xl" and ARGS[3] == "add" then return lpm.lite_xl_add(table.unpack(common.slice(ARGS, 4))) + elseif ARGS[2] == "lite-xl" and ARGS[3] == "rm" then return lpm.lite_xl_rm(table.unpack(common.slice(ARGS, 4))) elseif ARGS[2] == "lite-xl" then error("unknown lite-xl command: " .. ARGS[3]) - elseif ARGS[2] == "bottle" and ARGS[3] == "purge" then return lpm_bottle_purge(common.slice(ARGS, 4)) - elseif ARGS[2] == "run" then return lpm_lite_xl_run(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "switch" then return lpm_lite_xl_switch(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "purge" then lpm_purge() + elseif ARGS[2] == "bottle" and ARGS[3] == "purge" then return lpm.bottle_purge(common.slice(ARGS, 4)) + elseif ARGS[2] == "run" then return lpm.lite_xl_run(table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "switch" then return lpm.lite_xl_switch(table.unpack(common.slice(ARGS, 3))) + elseif ARGS[2] == "purge" then lpm.purge() else error("unknown command: " .. ARGS[2]) end if JSON then io.stdout:write(json.encode({ actions = actions, warnings = warnings })) @@ -2110,6 +2085,29 @@ local function run_command(ARGS) end +local status = 0 +local function error_handler(err) + local s, e + if err then s, e = err:find("%:%d+") end + local message = e and err:sub(e + 3) or err + if JSON then + if VERBOSE then + io.stderr:write(json.encode({ error = err, actions = actions, warnings = warnings, traceback = debug.traceback(nil, 2) }) .. "\n") + else + io.stderr:write(json.encode({ error = message or err, actions = actions, warnings = warnings }) .. "\n") + end + else + if err then io.stderr:write(colorize((not VERBOSE and message or err) .. "\n", "red")) end + if VERBOSE then io.stderr:write(debug.traceback(nil, 2) .. "\n") end + end + io.stderr:flush() + status = -1 +end +local function lock_warning() + log.warning("waiting for lpm global lock to be released (only one instance of lpm can be run at once)") +end + + xpcall(function() local ARGS = parse_arguments(ARGV, { json = "flag", userdir = "string", cachedir = "string", version = "flag", verbose = "flag", @@ -2117,7 +2115,7 @@ xpcall(function() remotes = "flag", ["ssl-certs"] = "string", force = "flag", arch = "array", ["assume-yes"] = "flag", ["no-install-optional"] = "flag", datadir = "string", binary = "string", trace = "flag", progress = "flag", symlink = "flag", reinstall = "flag", ["no-color"] = "flag", config = "string", table = "string", header = "string", - repository = "string", ephemeral = "flag", mask = "array", raw = "string", + repository = "string", ephemeral = "flag", mask = "array", raw = "string", plugin = "array", -- filtration flags author = "array", tag = "array", stub = "array", dependency = "array", status = "array", type = "array", name = "array" @@ -2133,7 +2131,7 @@ Usage: lpm COMMAND [...ARGUMENTS] [--json] [--userdir=directory] [--ssl-certs=directory/file] [--force] [--arch=]] .. _G.ARCH .. [[] [--assume-yes] [--no-install-optional] [--verbose] [--mod-version=3] [--datadir=directory] [--binary=path] [--symlink] [--post] [--reinstall] - [--no-color] [--table=...] + [--no-color] [--table=...] [--plugin=file] LPM is a package manager for `lite-xl`, written in C (and packed-in lua). @@ -2263,6 +2261,8 @@ Flags have the following effects: are those specified in this option. --ephemeral Designates a bottle as 'ephemeral', meaning that it is fully cleaned up when lpm exits. + --plugin Loads the specified plugin as part of lpm. Used + for customizing lpm for various tasks. The following flags are useful when listing plugins, or generating the plugin table. Putting a ! infront of the string will invert the filter. Multiple @@ -2423,7 +2423,7 @@ not commonly used publically. end repositories = {} - if ARGS[2] == "purge" then return lpm_purge() end + if ARGS[2] == "purge" then return lpm.purge() end local ssl_certs = ARGS["ssl-certs"] or os.getenv("SSL_CERT_DIR") or os.getenv("SSL_CERT_FILE") if ssl_certs then if ssl_certs == "noverify" then @@ -2465,6 +2465,16 @@ not commonly used publically. end end + for i,v in ipairs(ARGS["plugin"] or {}) do + local env = { + EXECUTABLE_EXTENSION = EXECUTABLE_EXTENSION, SHOULD_COLOR = SHOULD_COLOR, HOME = HOME, USERDIR = USERDIR, CACHEDIR = CACHEDIR, JSON = JSON, TABLE = TABLE, HEADER = HEADER, RAW = RAW, VERBOSE = VERBOSE, FILTRATION = FILTRATION, MOD_VERSION = MOD_VERSION, QUIET = QUIET, FORCE = FORCE, REINSTALL = REINSTALL, CONFIG = CONFIG, NO_COLOR = NO_COLOR, AUTO_PULL_REMOTES = AUTO_PULL_REMOTES, ARCH = ARCH, ASSUME_YES = ASSUME_YES, NO_INSTALL_OPTIONAL = NO_INSTALL_OPTIONAL, TMPDIR = TMPDIR, DATADIR = DATADIR, BINARY = BINARY, POST = POST, PROGRESS = PROGRESS, SYMLINK = SYMLINK, REPOSITORY = REPOSITORY, EPHEMERAL = EPHEMERAL, MASK = MASK, + Addon = Addon, Repository = Repository, LiteXL = LiteXL, Bottle = Bottle, lpm = lpm, common = common, json = json, log = log, + settings = settings, repositories = repositories, lite_xls = lite_xls, system_bottle = system_bottle, progress_bar_label = progress_bar_label, write_progress_bar, + } + setmetatable(env, { __index = _G }) + load(io.open(v):read("*all"), v, "bt", env)() + end + -- Small utility functions that don't play into the larger app; are used for testing -- or for handy scripts. if ARGS[2] == "test" or ARGS[2] == "exec" then @@ -2502,7 +2512,7 @@ not commonly used publically. for _, section in ipairs(common.concat(m.addons or {}, m["lite-xls"] or {})) do for _, file in ipairs(common.concat({ section }, section.files or {})) do if (not filter or (section.id and filter[section.id])) and file.url and file.checksum ~= "SKIP" and type(file.checksum) == "string" then - log_action("Computing checksum for " .. (section.id or section.version) .. " (" .. file.url .. ")...") + log.action("Computing checksum for " .. (section.id or section.version) .. " (" .. file.url .. ")...") local checksum = system.hash(common.get(file.url)) if computed[file.checksum] and computed[file.checksum] ~= checksum then error("can't update manifest; existing checksum " .. file.checksum .. " exists in two separate places that now have disparate checksum values") @@ -2522,7 +2532,7 @@ not commonly used publically. os.exit(0) end if ARGS[2] == "self-upgrade" then - lpm_self_upgrade(table.unpack(common.slice(ARGS, 3))) + lpm.self_upgrade(table.unpack(common.slice(ARGS, 3))) os.exit(0) end @@ -2530,7 +2540,7 @@ not commonly used publically. -- Base setup; initialize default repos if applicable, read them in. Determine Lite XL system binary if not specified, and pull in a list of all local lite-xl's. if engage_locks(function() settings = { lite_xls = {}, repositories = {}, installed = {}, version = VERSION } - lpm_repo_init(ARGS[2] == "init" and #ARGS > 2 and (ARGS[3] ~= "none" and common.map(common.slice(ARGS, 3), function(url) return Repository.url(url) end) or {}) or nil) + lpm.repo_init(ARGS[2] == "init" and #ARGS > 2 and (ARGS[3] ~= "none" and common.map(common.slice(ARGS, 3), function(url) return Repository.url(url) end) or {}) or nil) repositories, lite_xls = {}, {} if system.stat(CACHEDIR .. PATHSEP .. "settings.json") then settings = json.decode(common.read(CACHEDIR .. PATHSEP .. "settings.json")) end repositories = common.map(settings.repositories or {}, function(url) local repo = Repository.url(url) repo:parse_manifest() return repo end) @@ -2556,7 +2566,7 @@ not commonly used publically. if not system_lite_xl then system_lite_xl = detected_lite_xl table.insert(lite_xls, system_lite_xl) - lpm_lite_xl_save() + lpm.lite_xl_save() else lite_xls = common.grep(lite_xls, function(e) return e ~= system_lite_xl end) system_lite_xl = detected_lite_xl |