diff options
author | Adam <adamdharrison@gmail.com> | 2022-10-14 15:21:34 -0400 |
---|---|---|
committer | Adam <adamdharrison@gmail.com> | 2022-10-14 15:21:34 -0400 |
commit | 8ce76353963a58e5ca0758f240ccb62056126ceb (patch) | |
tree | a29c180803f5e7871ba1cb51ac32c09922b4c91a /lpm.lua | |
parent | 35a0aa145ebe7e46ae64ccfb09481be577b89000 (diff) | |
download | lite-xl-plugin-manager-8ce76353963a58e5ca0758f240ccb62056126ceb.tar.gz lite-xl-plugin-manager-8ce76353963a58e5ca0758f240ccb62056126ceb.zip |
Reorganized things.
Diffstat (limited to 'lpm.lua')
-rw-r--r-- | lpm.lua | 1612 |
1 files changed, 0 insertions, 1612 deletions
diff --git a/lpm.lua b/lpm.lua deleted file mode 100644 index c0752f0..0000000 --- a/lpm.lua +++ /dev/null @@ -1,1612 +0,0 @@ -setmetatable(_G, { __index = function(t, k) if not rawget(t, k) then error("cannot get undefined global variable: " .. k, 2) end end, __newindex = function(t, k) error("cannot set global variable: " .. k, 2) end }) - --- Begin rxi JSON library. -local json = { _version = "0.1.2" } -local encode -local escape_char_map = { - [ "\\" ] = "\\", - [ "\"" ] = "\"", - [ "\b" ] = "b", - [ "\f" ] = "f", - [ "\n" ] = "n", - [ "\r" ] = "r", - [ "\t" ] = "t", -} - -local escape_char_map_inv = { [ "/" ] = "/" } -for k, v in pairs(escape_char_map) do - escape_char_map_inv[v] = k -end - - -local function escape_char(c) - return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) -end - - -local function encode_nil(val) - return "null" -end - - -local function encode_table(val, stack) - local res = {} - stack = stack or {} - - -- Circular reference? - if stack[val] then error("circular reference") end - - stack[val] = true - - if rawget(val, 1) ~= nil or next(val) == nil then - -- Treat as array -- check keys are valid and it is not sparse - local n = 0 - for k in pairs(val) do - if type(k) ~= "number" then - error("invalid table: mixed or invalid key types") - end - n = n + 1 - end - if n ~= #val then - error("invalid table: sparse array") - end - -- Encode - for i, v in ipairs(val) do - table.insert(res, encode(v, stack)) - end - stack[val] = nil - return "[" .. table.concat(res, ",") .. "]" - - else - -- Treat as an object - for k, v in pairs(val) do - if type(k) ~= "string" then - error("invalid table: mixed or invalid key types") - end - table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) - end - stack[val] = nil - return "{" .. table.concat(res, ",") .. "}" - end -end - - -local function encode_string(val) - return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' -end - - -local function encode_number(val) - -- Check for NaN, -inf and inf - if val ~= val or val <= -math.huge or val >= math.huge then - error("unexpected number value '" .. tostring(val) .. "'") - end - return string.format("%.14g", val) -end - - -local type_func_map = { - [ "nil" ] = encode_nil, - [ "table" ] = encode_table, - [ "string" ] = encode_string, - [ "number" ] = encode_number, - [ "boolean" ] = tostring, -} - - -encode = function(val, stack) - local t = type(val) - local f = type_func_map[t] - if f then - return f(val, stack) - end - error("unexpected type '" .. t .. "'") -end - - -function json.encode(val) - return ( encode(val) ) -end - -local parse - -local function create_set(...) - local res = {} - for i = 1, select("#", ...) do - res[ select(i, ...) ] = true - end - return res -end - -local space_chars = create_set(" ", "\t", "\r", "\n") -local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") -local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") -local literals = create_set("true", "false", "null") - -local literal_map = { - [ "true" ] = true, - [ "false" ] = false, - [ "null" ] = nil, -} - - -local function next_char(str, idx, set, negate) - for i = idx, #str do - if set[str:sub(i, i)] ~= negate then - return i - end - end - return #str + 1 -end - - -local function decode_error(str, idx, msg) - local line_count = 1 - local col_count = 1 - for i = 1, idx - 1 do - col_count = col_count + 1 - if str:sub(i, i) == "\n" then - line_count = line_count + 1 - col_count = 1 - end - end - error( string.format("%s at line %d col %d", msg, line_count, col_count) ) -end - - -local function codepoint_to_utf8(n) - -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa - local f = math.floor - if n <= 0x7f then - return string.char(n) - elseif n <= 0x7ff then - return string.char(f(n / 64) + 192, n % 64 + 128) - elseif n <= 0xffff then - return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) - elseif n <= 0x10ffff then - return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, - f(n % 4096 / 64) + 128, n % 64 + 128) - end - error( string.format("invalid unicode codepoint '%x'", n) ) -end - - -local function parse_unicode_escape(s) - local n1 = tonumber( s:sub(1, 4), 16 ) - local n2 = tonumber( s:sub(7, 10), 16 ) - -- Surrogate pair? - if n2 then - return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) - else - return codepoint_to_utf8(n1) - end -end - - -local function parse_string(str, i) - local res = "" - local j = i + 1 - local k = j - - while j <= #str do - local x = str:byte(j) - - if x < 32 then - decode_error(str, j, "control character in string") - - elseif x == 92 then -- `\`: Escape - res = res .. str:sub(k, j - 1) - j = j + 1 - local c = str:sub(j, j) - if c == "u" then - local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) - or str:match("^%x%x%x%x", j + 1) - or decode_error(str, j - 1, "invalid unicode escape in string") - res = res .. parse_unicode_escape(hex) - j = j + #hex - else - if not escape_chars[c] then - decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") - end - res = res .. escape_char_map_inv[c] - end - k = j + 1 - - elseif x == 34 then -- `"`: End of string - res = res .. str:sub(k, j - 1) - return res, j + 1 - end - - j = j + 1 - end - - decode_error(str, i, "expected closing quote for string") -end - - -local function parse_number(str, i) - local x = next_char(str, i, delim_chars) - local s = str:sub(i, x - 1) - local n = tonumber(s) - if not n then - decode_error(str, i, "invalid number '" .. s .. "'") - end - return n, x -end - - -local function parse_literal(str, i) - local x = next_char(str, i, delim_chars) - local word = str:sub(i, x - 1) - if not literals[word] then - decode_error(str, i, "invalid literal '" .. word .. "'") - end - return literal_map[word], x -end - - -local function parse_array(str, i) - local res = {} - local n = 1 - i = i + 1 - while 1 do - local x - i = next_char(str, i, space_chars, true) - -- Empty / end of array? - if str:sub(i, i) == "]" then - i = i + 1 - break - end - -- Read token - x, i = parse(str, i) - res[n] = x - n = n + 1 - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "]" then break end - if chr ~= "," then decode_error(str, i, "expected ']' or ','") end - end - return res, i -end - - -local function parse_object(str, i) - local res = {} - i = i + 1 - while 1 do - local key, val - i = next_char(str, i, space_chars, true) - -- Empty / end of object? - if str:sub(i, i) == "}" then - i = i + 1 - break - end - -- Read key - if str:sub(i, i) ~= '"' then - decode_error(str, i, "expected string for key") - end - key, i = parse(str, i) - -- Read ':' delimiter - i = next_char(str, i, space_chars, true) - if str:sub(i, i) ~= ":" then - decode_error(str, i, "expected ':' after key") - end - i = next_char(str, i + 1, space_chars, true) - -- Read value - val, i = parse(str, i) - -- Set - res[key] = val - -- Next token - i = next_char(str, i, space_chars, true) - local chr = str:sub(i, i) - i = i + 1 - if chr == "}" then break end - if chr ~= "," then decode_error(str, i, "expected '}' or ','") end - end - return res, i -end - - -local char_func_map = { - [ '"' ] = parse_string, - [ "0" ] = parse_number, - [ "1" ] = parse_number, - [ "2" ] = parse_number, - [ "3" ] = parse_number, - [ "4" ] = parse_number, - [ "5" ] = parse_number, - [ "6" ] = parse_number, - [ "7" ] = parse_number, - [ "8" ] = parse_number, - [ "9" ] = parse_number, - [ "-" ] = parse_number, - [ "t" ] = parse_literal, - [ "f" ] = parse_literal, - [ "n" ] = parse_literal, - [ "[" ] = parse_array, - [ "{" ] = parse_object, -} - - -parse = function(str, idx) - local chr = str:sub(idx, idx) - local f = char_func_map[chr] - if f then - return f(str, idx) - end - decode_error(str, idx, "unexpected character '" .. chr .. "'") -end - - -function json.decode(str) - if type(str) ~= "string" then - error("expected argument of type string, got " .. type(str)) - end - local res, idx = parse(str, next_char(str, 1, space_chars, true)) - idx = next_char(str, idx, space_chars, true) - if idx <= #str then - decode_error(str, idx, "trailing garbage") - end - return res -end - --- End JSON library. -local function is_commit_hash(hash) - return #hash == 40 and not hash:find("[^a-z0-9]") -end - - -local common = {} -function common.merge(dst, src) for k, v in pairs(src) do dst[k] = v end return dst end -function common.map(l, p) local t = {} for i, v in ipairs(l) do table.insert(t, p(v)) end return t end -function common.flat_map(l, p) local t = {} for i, v in ipairs(l) do local r = p(v) for k, w in ipairs(r) do table.insert(t, w) end end return t end -function common.concat(t1, t2) local t = {} for i, v in ipairs(t1) do table.insert(t, v) end for i, v in ipairs(t2) do table.insert(t, v) end return t end -function common.grep(l, p) local t = {} for i, v in ipairs(l) do if p(v) then table.insert(t, v) end end return t end -function common.first(l, p) for i, v in ipairs(l) do if p(v) then return v end end end -function common.slice(t, i, l) local n = {} for j = i, l ~= nil and (i - l) or #t do table.insert(n, t[j]) end return n end -function common.join(j, l) local s = "" for i, v in ipairs(l) do if i > 1 then s = s .. j .. v else s = v end end return s end -function common.sort(t, f) table.sort(t, f) return t end -function common.write(path, contents) local f, err = io.open(path, "wb") if not f then error("can't write to " .. path .. ": " .. err) end f:write(contents) f:flush() end -function common.split(splitter, str) - local o = 1 - local res = {} - while true do - local s, e = str:find(splitter, o) - table.insert(res, str:sub(o, s and (s - 1) or #str)) - if not s then break end - o = e + 1 - end - return table.unpack(res) -end - -function common.dirname(path) local s = path:reverse():find("[/\\]") if not s then return path end return path:sub(1, #path - s) end -function common.basename(path) local s = path:reverse():find("[/\\]") if not s then return path end return path:sub(#path - s + 2) end -function common.path(exec) return common.first(common.map({ common.split(":", os.getenv("PATH")) }, function(e) return e .. PATHSEP .. exec end), function(e) return system.stat(e) end) end -function common.rmrf(root) - local info = root and root ~= "" and system.stat(root) - if not info then return end - if info.type == "file" or info.symlink then return os.remove(root) end - for i,v in ipairs(system.ls(root)) do common.rmrf(root .. PATHSEP .. v) end - system.rmdir(root) -end -function common.mkdirp(path) - local stat = system.stat(path) - if stat and stat.type == "dir" then return true end - if stat and stat.type == "file" then error("path " .. path .. " exists") end - local target - for _, dirname in ipairs({ common.split("[/\\]", path) }) do - target = target and target .. PATHSEP .. dirname or dirname - if target ~= "" and not system.stat(target) then system.mkdir(target) end - end -end -function common.copy(src, dst) - local src_stat, dst_stat = system.stat(src), system.stat(dst) - if not src_stat then error("can't find " .. src) end - if dst_stat and dst_stat.type == "dir" then return common.copy(src, dst .. PATHSEP .. common.basename(src)) end - if src_stat.type == "dir" then - common.mkdirp(dst) - for i, file in ipairs(system.ls(src)) do common.copy(src .. PATHSEP .. file, dst .. PATHSEP .. file) end - else - local src_io, err1 = io.open(src, "rb") - if err1 then error("can't open for reading " .. src .. ": " .. err1) end - local dst_io, err2 = io.open(dst, "wb") - if err2 then error("can't open for writing " .. dst .. ": " .. err2) end - while true do - local chunk = src_io:read(64*1024) - if not chunk then break end - dst_io:write(chunk) - end - dst_io:close() - end -end -function common.rename(src, dst) - local _, err = os.rename(src, dst) - if err then error("can't rename file " .. src .. " to " .. dst .. ": " .. err) end -end -function common.reset(path, ref, type) - if is_commit_hash(ref) then - system.reset(path, ref, type) - else - if not pcall(system.reset, path, "refs/tags/" .. ref, type) then system.reset(path, "refs/remotes/origin/" .. ref, type) end - end -end - -local HOME, USERDIR, CACHEDIR, JSON, VERBOSE, MOD_VERSION, QUIET, FORCE, AUTO_PULL_REMOTES, ARCH, ASSUME_YES, NO_INSTALL_OPTIONAL, TMPDIR, repositories, lite_xls, system_bottle - -local Plugin, Repository, LiteXL, Bottle = {}, {}, {}, {} - -local actions, warnings = {}, {} -local function log_action(message) - if JSON then table.insert(actions, message) end - if not QUIET then io.stderr:write(message .. "\n") end -end -local function log_warning(message) - if JSON then table.insert(warnings, message) end - if not QUIET then io.stderr:write("warning: " .. message .. "\n") end -end -local function fatal_warning(message) - if not FORCE then error(message .. "; use --force to override") else log_warning(message) end -end -local function prompt(message) - io.stderr:write(message .. " [Y/n]: ") - if ASSUME_YES then io.stderr:write("Y\n") return true end - local response = io.stdin:read("*line") - return not response:find("%S") or response:find("^%s*[yY]%s*$") -end - -function common.get(source, target, checksum) - if not checksum then return system.get(source, target) end - if not system.stat(CACHEDIR .. PATHSEP .. "files") then common.mkdirp(CACHEDIR .. PATHSEP .. "files") end - local cache_path = CACHEDIR .. PATHSEP .. "files" .. PATHSEP .. checksum - if not system.stat(cache_path) then - system.get(source, cache_path) - if system.hash(cache_path, "file") ~= checksum then fatal_warning("checksum doesn't match for " .. source) end - end - common.copy(cache_path, target) -end - - -local function compare_version(a, b) -- compares semver - if not a or not b then return false end - local _, _, majora, minora, revisiona = tostring(a):find("(%d+)%.?(%d*)%.?(%d*)") - local _, _, majorb, minorb, revisionb = tostring(b):find("(%d+)%.?(%d*)%.?(%d*)") - if majora == nil then error("can't parse version " .. a) end - if majorb == nil then error("can't parse version " .. b) end - majora, minora, revisiona = majora or 0, minora or 0, revisiona or 0 - majorb, minorb, revisionb = majorb or 0, minorb or 0, revisionb or 0 - if majora ~= majorb then return tonumber(majora) < tonumber(majorb) and -1 or 1 end - if minora ~= minorb then return tonumber(minora) < tonumber(minorb) and -1 or 1 end - if revisiona ~= revisionb then return tonumber(revisiona) < tonumber(revisionb) and -1 or 1 end - return 0 -end - -local function match_version(version, pattern) - if not pattern then return true end - if pattern:find("^>=") then return compare_version(version, pattern:sub(3)) >= 0 end - if pattern:find("^<=") then return compare_version(version, pattern:sub(3)) <= 0 end - if pattern:find("^<") then return compare_version(version, pattern:sub(2)) == -1 end - if pattern:find("^>") then return compare_version(version, pattern:sub(2)) == 1 end - if pattern:find("^=") then return compare_version(version, pattern:sub(2)) == 0 end - return version == pattern -end - - - -function Plugin.__index(self, idx) return rawget(self, idx) or Plugin[idx] end -function Plugin.new(repository, metadata) - local type = metadata.type or "plugin" - local folder = metadata.type == "library" and "libraries" or "plugins" - if metadata.path then metadata.path = metadata.path:gsub("/", PATHSEP) end - local self = setmetatable(common.merge({ - repository = repository, - tags = {}, - type = type, - path = nil, - remote = nil, - version = "1.0", - dependencies = {}, - local_path = repository and (repository.local_path .. PATHSEP .. (repository.commit or repository.branch) .. (metadata.path and (PATHSEP .. metadata.path:gsub("^/", "")) or "")), - }, metadata), Plugin) - -- Directory. - self.organization = metadata.organization or (((self.files and #self.files > 0) or self.remote or (not self.path and not self.url)) and "complex" or "singleton") - return self -end - -function Plugin:get_install_path(bottle) - local folder = self.type == "library" and "libraries" or "plugins" - local path = (bottle.local_path and (bottle.local_path .. PATHSEP .. "user") or USERDIR) .. PATHSEP .. folder .. PATHSEP .. (self.path and common.basename(self.path):gsub("%.lua$", "") or self.name) - if self.organization == "singleton" then path = path .. ".lua" end - return path -end - -function Plugin:is_core(bottle) return self.type == "core" end -function Plugin:is_installed(bottle) return self:is_core(bottle) or (bottle.lite_xl:is_compatible(self) and system.stat(self:get_install_path(bottle))) end -function Plugin:is_incompatible(plugin) return self.dependencies[plugin.name] and not match_version(plugin.version, dependencies[plugin.name]) end - -function Plugin:get_compatibilities(bottle) - local compatible_plugins, incompatible_plugins = {}, {} - local installed_plugins = bottle:installed_plugins() - for plugin, v in pairs(self.dependencies) do - local potential_plugins = { bottle:get_plugin(plugin, v.version, { mod_version = bottle.lite_xl.mod_version }) } - for i, potential_plugin in ipairs(potential_plugins) do - local incomaptibilities = common.grep(installed_plugins, function(p) return p:is_incompatible(potential_plugin) end) - if #incomaptibilities == 0 then - if not compatible_plugins[plugin] or - potential_plugin:is_installed(bottle) or - (compare_version(compatible_plugins[plugin].version, potential_plugin.version) and not compatible_plugins[plugin]:is_installed(bottle)) - then - compatible_plugins[plugin] = potential_plugin - end - else - incompatible_plugins[plugin] = incompatibilities - end - end - end - return compatible_plugins, incompatible_plugins -end - - - -function Plugin:install(bottle, installing) - if self:is_installed(bottle) then log_warning("plugin " .. self.name .. " is already installed") return end - local install_path = self:get_install_path(bottle) - local temporary_install_path = TMPDIR .. PATHSEP .. install_path:sub(#USERDIR + 2) - local status, err = pcall(function() - installing = installing or {} - installing[self.name] = true - local compatible, incompatible = self:get_compatibilities(bottle) - for plugin, version in pairs(self.dependencies) do - if incompatible[plugin] then error("can't install " .. self.name .. ": incompatible with " .. incompatible[plugin][1].name .. ":" .. incompatible[plugin][1].version) end - end - for plugin, v in pairs(self.dependencies) do - if not compatible[plugin] then error("can't find dependency " .. plugin .. (v.version and (":" .. v.version) or "")) end - end - for plugin, v in pairs(self.dependencies) do - if not compatible[plugin]:is_core(bottle) and not compatible[plugin]:is_installed(bottle) then - if installing[plugin] then - error("circular dependency detected in " .. self.name .. ": requires " .. plugin .. " but, " .. plugin .. " requires " .. self.name) - end - if not NO_INSTALL_OPTIONAL and (not v.optional or prompt(plugin .. " is an optional dependency of " .. self.name .. ". Should we install it?")) then - compatible[plugin]:install(bottle, installing) - end - end - end - common.mkdirp(common.dirname(temporary_install_path)) - if self.status == "upgradable" then - log_action("Upgrading " .. self.organization .. "plugin located at " .. self.local_path .. " to " .. install_path) - common.rmrf(install_path) - else - log_action("Installing " .. self.organization .. " plugin located at " .. self.local_path .. " to " .. install_path) - end - - if self.organization == "complex" and self.path and system.stat(self.local_path).type ~= "dir" then common.mkdirp(install_path) end - if self.url then - log_action("Downloading file " .. self.url .. "...") - 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, path, self.checksum) - log_action("Downloaded file " .. self.url .. " to " .. path) - if system.hash(path, "file") ~= self.checksum then fatal_warning("checksum doesn't match for " .. path) end - elseif self.remote then - log_action("Cloning repository " .. self.remote .. " into " .. install_path) - common.mkdirp(temporary_install_path) - local _, _, url, branch = self.remote:find("^(.*):(.*)$") - system.init(temporary_install_path, url) - common.reset(temporary_install_path, branch) - else - local path = install_path .. (self.organization == 'complex' and self.path and system.stat(self.local_path).type ~= "dir" and (PATHSEP .. "init.lua") or "") - 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 "") - log_action("Copying " .. self.local_path .. " to " .. path) - common.copy(self.local_path, temporary_path) - end - for i,file in ipairs(self.files or {}) do - if not file.arch or file.arch == ARCH then - if not NO_INSTALL_OPTIONAL and (not file.optional or prompt(common.basename(file.url) .. " is an optional dependency of " .. self.name .. ". Should we install it?")) then - if not file.checksum then error("requires a checksum") end - local path = install_path .. PATHSEP .. (file.path or common.basename(file.url)) - local temporary_path = temporary_install_path .. PATHSEP .. (file.path or common.basename(file.url)) - log_action("Downloading file " .. file.url .. "...") - common.get(file.url, temporary_path, file.checksum) - log_action("Downloaded file " .. file.url .. " to " .. path) - if system.hash(temporary_path, "file") ~= file.checksum then fatal_warning("checksum doesn't match for " .. path) end - end - end - end - end) - if not status then - common.rmrf(temporary_install_path) - error(err) - else - common.rmrf(install_path) - common.mkdirp(common.dirname(install_path)) - common.rename(temporary_install_path, install_path) - end -end - -function Plugin:depends_on(plugin) - if self.dependencies[plugin.name] and self.dependencies[plugin.name].optional ~= true then return true end - for i,v in ipairs(plugin.provides or {}) do if self.dependencies[v] and self.dependencies[v].optional ~= true then return true end end - return false -end - -function Plugin:uninstall(bottle) - local install_path = self:get_install_path(bottle) - if self:is_core(bottle) then error("can't uninstall " .. self.name .. " is a core plugin") end - log_action("Uninstalling plugin located at " .. install_path) - local incompatible_plugins = common.grep(bottle:installed_plugins(), function(p) return p:depends_on(self) end) - if #incompatible_plugins == 0 or prompt(self.name .. " is depended upon by " .. common.join(", ", common.map(incompatible_plugins, function(p) return p.name end)) .. ". Remove as well?") then - for i,plugin in ipairs(incompatible_plugins) do - if not plugin:uninstall(bottle) then return false end - end - common.rmrf(install_path) - return true - end - return false -end - - -function Repository.__index(self, idx) return rawget(self, idx) or Repository[idx] end -function Repository.new(hash) - if not hash.remote then error("requires a remote") end - if not hash.remote:find("^https?:") and not hash.remote:find("^file:") then error("only repositories with http and file transports are supported (" .. hash.remote .. ")") end - local self = setmetatable({ - commit = hash.commit, - remote = hash.remote, - branch = hash.branch, - plugins = nil, - lite_xls = {}, - local_path = CACHEDIR .. PATHSEP .. "repos" .. PATHSEP .. system.hash(hash.remote), - last_retrieval = nil - }, Repository) - if system.stat(self.local_path) and not self.commit and not self.branch then - -- In the case where we don't have a branch, and don't have a commit, check for the presence of `master` and `main`. - if system.stat(self.local_path .. PATHSEP .. "master") then - self.branch = "master" - elseif system.stat(self.local_path .. PATHSEP .. "main") then - self.branch = "main" - else - error("can't find branch for " .. self.remote) - end - end - return self -end - -function Repository.url(url) - if type(url) == "table" then return url.remote .. ":" .. (url.branch or url.commit) end - local e = url:reverse():find(":") - local s = e and (#url - e + 1) - local remote, branch_or_commit = url:sub(1, s and (s-1) or #url), s and url:sub(s+1) - if remote == "https" or remote == "file" then remote, branch_or_commit = url, nil end - if branch_or_commit and is_commit_hash(branch_or_commit) then - return Repository.new({ remote = remote, commit = branch_or_commit }) - end - return Repository.new({ remote = remote, branch = branch_or_commit }) -end - -function Repository:parse_manifest(already_pulling) - if self.manifest then return self.manifest, self.remotes end - if system.stat(self.local_path) and system.stat(self.local_path .. PATHSEP .. (self.commit or self.branch)) then - self.manifest_path = self.local_path .. PATHSEP .. (self.commit or self.branch) .. PATHSEP .. "manifest.json" - if not system.stat(self.manifest_path) then self:generate_manifest() end - self.manifest = json.decode(io.open(self.manifest_path, "rb"):read("*all")) - self.plugins = {} - self.remotes = {} - for i, metadata in ipairs(self.manifest["plugins"] or {}) do - if metadata.remote then - local _, _, url, branch_or_commit = metadata.remote:find("^(.-):?(.*)?$") - if branch_or_commit and is_commit_hash(branch_or_commit) then - repo = Repository.new({ remote = url, commit = branch_or_commit }) - table.insert(remotes, repo) - table.insert(self.plugins, Plugin.new(self, metadata)) - else - -- log_warning("plugin " .. metadata.name .. " specifies remote as source, but isn't a commit") - end - else - table.insert(self.plugins, Plugin.new(self, metadata)) - end - end - for i, metadata in ipairs(self.manifest["lite-xls"] or {}) do - if metadata.remote then - local _, _, url, branch_or_commit = metadata.remote:find("^(.-):?(.*)?$") - if branch_or_commit and is_commit_hash(branch_or_commit) then - repo = Repository.new({ remote = url, commit = branch_or_commit }) - table.insert(remotes, repo) - table.insert(self.lite_xls, LiteXL.new(self, metadata)) - else - -- log_warning("plugin " .. metadata.name .. " specifies remote as source, but isn't a commit") - end - else - table.insert(self.lite_xls, LiteXL.new(self, metadata)) - end - end - self.remotes = common.map(self.manifest["remotes"] or {}, function(r) return Repository.url(r) end) - end - return self.manifest, self.remotes -end - - --- in the cases where we don't have a manifest, assume generalized structure, take plugins folder, trawl through it, build manifest that way --- assuming each .lua file under the `plugins` folder is a plugin. also parse the README, if present, and see if any of the plugins -function Repository:generate_manifest() - if not self.commit and not self.branch then error("requires an instantiation") end - local path = self.local_path .. PATHSEP .. (self.commit or self.branch) - local plugin_dir = system.stat(path .. PATHSEP .. "plugins") and PATHSEP .. "plugins" .. PATHSEP or PATHSEP - local plugins, plugin_map = {}, {} - if system.stat(path .. PATHSEP .. "README.md") then -- If there's a README, parse it for a table like in our primary repository. - for line in io.lines(path .. PATHSEP .. "README.md") do - local _, _, name, path, description = line:find("^%s*%|%s*%[`([%w_]+)%??.-`%]%((.-)%).-%|%s*(.-)%s*%|%s*$") - if name then - plugin_map[name] = { name = name, description = description, files = {} } - if path:find("^http") then - if path:find("%.lua") then - plugin_map[name].url = path - local file = common.get(path) - plugin_map[name].checksum = system.hash(file) - else - plugin_map[name].remote = path - end - else - plugin_map[name].path = path:gsub("%?.*$", "") - end - end - end - end - for i, file in ipairs(system.ls(path .. plugin_dir)) do - if file:find("%.lua$") then - local plugin = { description = nil, files = {}, name = common.basename(file):gsub("%.lua$", ""), dependencies = {}, mod_version = 3, version = "1.0", tags = {}, path = plugin_dir .. file } - for line in io.lines(path .. plugin_dir .. file) do - local _, _, mod_version = line:find("%-%-.*mod%-version:%s*(%w+)") - if mod_version then plugin.mod_version = mod_version end - local _, _, required_plugin = line:find("require [\"']plugins.([%w_]+)") - if required_plugin then if required_plugin ~= plugin.name then plugin.dependencies[required_plugin] = ">=1.0" end end - end - if plugin_map[plugin.name] then - plugin = common.merge(plugin, plugin_map[plugin.name]) - plugin_map[plugin.name].plugin = plugin - end - table.insert(plugins, plugin) - end - end - for k, v in pairs(plugin_map) do - if not v.plugin then - table.insert(plugins, common.merge({ dependencies = {}, mod_version = self.branch == "master" and 2 or 3, version = "1.0", tags = {} }, v)) - end - end - common.write(path .. PATHSEP .. "manifest.json", json.encode({ plugins = plugins })) -end - -function Repository:add(pull_remotes) - -- If neither specified then pull onto `master`, and check the main branch name, and move if necessary. - if not self.branch and not self.commit then - local path = self.local_path .. PATHSEP .. "master" - common.mkdirp(path) - log_action("Retrieving " .. self.remote .. ":master/main...") - system.init(path, self.remote) - system.fetch(path) - if not pcall(system.reset, path, "refs/remotes/origin/master", "hard") then - if pcall(system.reset, path, "refs/remotes/origin/main", "hard") then - common.rename(path, self.local_path .. PATHSEP .. "main") - self.branch = "main" - else - error("can't find master or main.") - end - else - self.branch = "master" - end - log_action("Retrieved " .. self.remote .. ":master/main.") - else - local path = self.local_path .. PATHSEP .. (self.commit or self.branch) - common.mkdirp(path) - log_action("Retrieving " .. self.remote .. ":" .. (self.commit or self.branch) .. "...") - system.init(path, self.remote) - system.fetch(path) - common.reset(path, self.commit or self.branch, "hard") - log_action("Retrieved " .. self:url() .. "...") - self.manifest = nil - end - local manifest, remotes = self:parse_manifest() - if pull_remotes then -- any remotes we don't have in our listing, call add, and add into the list - for i, remote in ipairs(remotes) do - if not common.first(repositories, function(repo) return repo.remote == remote.remote and repo.branch == remote.branch and repo.commit == remote.commit end) then - remote:add(pull_remotes == "recursive" and "recursive" or false) - table.insert(repositories, remote) - end - end - end - return self -end - - -function Repository:update(pull_remotes) - local manifest, remotes = self:parse_manifest() - if self.branch then - local path = self.local_path .. PATHSEP .. self.branch - system.fetch(path) - common.reset(path, self.branch, "hard") - log_action("Updated " .. self:url()) - self.manifest = nil - manifest, remotes = self:parse_manifest() - end - if pull_remotes then -- any remotes we don't have in our listing, call add, and add into the list - for i, remote in ipairs(remotes) do - if common.first(repositories, function(repo) return repo.remote == remote.remote and repo.branch == remote.branch and repo.commit == remote.comit end) then - remote:add(pull_remotes == "recursive" and "recursive" or false) - table.insert(repositories, remote) - end - end - end -end - - -function Repository:remove() - common.rmrf(self.local_path .. PATHSEP .. (self.commit or self.branch)) - if #system.ls(self.local_path) == 0 then common.rmrf(self.local_path) end -end - - -function LiteXL.__index(t, k) return LiteXL[k] end -function LiteXL.new(repository, metadata) - if not metadata.version then error("lite-xl entry requires a version") end - local self = setmetatable({ - repository = repository, - version = metadata.version, - remote = metadata.remote, - url = metadata.url, - tags = metadata.tags or {}, - mod_version = metadata.mod_version, - path = metadata.path, - files = metadata.files or {} - }, LiteXL) - self.hash = system.hash((repository and repository:url() or "") .. "-" .. metadata.version .. common.join("", common.map(self.files, function(f) return f.checksum end))) - self.local_path = self:is_local() and self.path or (CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. self.version .. PATHSEP .. self.hash) - return self -end - -function LiteXL:get_binary_path() return self.local_path .. PATHSEP .. "lite-xl" end -function LiteXL:get_data_directory() return self.local_path .. PATHSEP .. "data" end -function LiteXL:is_system() return system_bottle and system_bottle.lite_xl == self end -function LiteXL:is_local() return not self.repository and self.path end -function LiteXL:is_compatible(plugin) return compare_version(self.mod_version, plugin.mod_version) == 0 end -function LiteXL:is_installed() return system.stat(self.local_path) end - -function LiteXL:install() - 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") - if not executable then error("can't find system lite-xl executable") end - local stat = system.stat(executable) - executable = stat.symlink and stat.symlink or executable - datadir = common.dirname(executable) .. PATHSEP .. "data" - if not system.stat(datadir) then error("can't find system lite-xl data dir") end - common.copy(executable, self.local_path .. PATHSEP .. "lite-xl") - system.chmod(self.local_path .. PATHSEP .. "lite-xl", 448) -- chmod to rwx------- - common.copy(datadir, self.local_path .. PATHSEP .. "data") - elseif self.path and not self.repository then -- local repository - system.symlink(self:get_binary_path(), self.path .. PATHSEP .. "lite_xl") - else - if self.remote then - system.init(self.local_path, self.remote) - common.reset(self.local_path, self.commit or self.branch) - end - for i,file in ipairs(self.files or {}) do - if file.arch and file.arch == ARCH then - if not file.checksum then error("requires a checksum") end - 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 .. "...") - common.get(file.url, path, file.checksum) - log_action("Downloaded file " .. file.url .. " to " .. path) - if system.hash(path, "file") ~= file.checksum then fatal_warning("checksum doesn't match for " .. path) end - if archive then - log_action("Extracting file " .. basename .. " in " .. self.local_path) - system.extract(path, self.local_path) - end - end - end - end - if not system.stat(self.local_path .. PATHSEP .. "lite-xl") then error("can't find executable for lite-xl " .. self.version) end -end - -function LiteXL:uninstall() - if not system.stat(self.local_path) then error("lite-xl " .. self.version .. " not installed") end - common.rmrf(self.local_path) -end - - -function Bottle.__index(t, k) return Bottle[k] end -function Bottle.new(lite_xl, plugins, is_system) - local self = setmetatable({ - lite_xl = lite_xl, - plugins = plugins, - is_system = is_system - }, Bottle) - if not is_system then - table.sort(self.plugins, function(a, b) return (a.name .. ":" .. a.version) < (b.name .. ":" .. b.version) end) - self.hash = system.hash(lite_xl.version .. " " .. common.join(" ", common.map(self.plugins, function(p) return p.name .. ":" .. p.version end))) - self.local_path = CACHEDIR .. PATHSEP .. "bottles" .. PATHSEP .. self.hash - end - return self -end - -function Bottle:is_constructed() return self.is_system or system.stat(self.local_path) end - -function Bottle:construct() - if self.is_system then error("system bottle cannot be constructed") end - if self:is_constructed() then error("bottle " .. self.hash .. " already constructed") end - if not self.lite_xl:is_installed() then self.lite_xl:install() end - common.mkdirp(self.local_path .. PATHSEP .. "user") - common.copy(self.lite_xl.local_path .. PATHSEP .. "lite-xl", self.local_path .. PATHSEP .. "lite-xl") - system.chmod(self.local_path .. PATHSEP .. "lite-xl", 448) -- chmod to rwx------- - common.copy(self.lite_xl.local_path .. PATHSEP .. "data", self.local_path .. PATHSEP .. "data") - for i,plugin in ipairs(self.plugins) do plugin:install(self) end -end - -function Bottle:destruct() - if self.is_system then error("system bottle cannot be destructed") end - if not self:is_constructed() then error("lite-xl " .. self.version .. " not constructed") end - common.rmrf(self.local_path) -end - -function Bottle:run(args) - args = args or {} - if self.is_system then error("system bottle cannot be run") end - os.execute(self.local_path .. PATHSEP .. "lite-xl", table.unpack(args)) -end - -function Bottle:all_plugins() - local t = common.flat_map(repositories, function(r) return r.plugins end) - local hash = { } - for i, v in ipairs(t) do hash[v.name] = v end - local plugin_paths = { - (self.local_path and (self.local_path .. PATHSEP .. "user") or USERDIR) .. PATHSEP .. "plugins", - self.lite_xl:get_data_directory() .. PATHSEP .. "plugins" - } - for i, plugin_path in ipairs(common.grep(plugin_paths, function(e) return system.stat(e) end)) do - for k, v in ipairs(common.grep(system.ls(plugin_path), function(e) return not hash[e:gsub("%.lua$", "")] end)) do - table.insert(t, Plugin.new(nil, { - name = v:gsub("%.lua$", ""), - type = i == 2 and "core", - organization = (v:find("%.lua$") and "singleton" or "complex"), - mod_version = self.lite_xl.mod_version, - path = "plugins/" .. v, - version = "1.0" - })) - end - end - return t -end - -function Bottle:installed_plugins() - return common.grep(self:all_plugins(), function(p) return p:is_installed(self) end) -end - -function Bottle:get_plugin(name, version, filter) - local candidates = {} - local wildcard = name:find("%*$") - filter = filter or {} - for i,plugin in ipairs(self:all_plugins()) do - if not version and plugin.provides then - for k, provides in ipairs(plugin.provides) do - if provides == name then - table.insert(candidates, plugin) - end - end - end - if (plugin.name == name or (wildcard and plugin.name:find("^" .. name:sub(1, #name - 1)))) and match_version(plugin.version, version) then - if (not filter.mod_version or not plugin.mod_version or tonumber(plugin.mod_version) == tonumber(filter.mod_version)) then - table.insert(candidates, plugin) - end - end - end - return table.unpack(common.sort(candidates, function (a,b) return a.version < b.version end)) -end - - -local function get_repository(url) - if not url then error("requires a repository url") end - local r = Repository.url(url) - for i,v in ipairs(repositories) do - if v.remote == r.remote and v.branch == r.branch and v.commit == r.commit then return i, v end - end - return nil -end - - -local DEFAULT_REPOS -local function lpm_repo_init() - DEFAULT_REPOS = { Repository.url("https://github.com/adamharrison/lite-xl-plugin-manager.git:latest") } - if not system.stat(CACHEDIR .. PATHSEP .. "repos") then - for i, repository in ipairs(DEFAULT_REPOS) do - if not system.stat(repository.local_path) or not system.stat(repository.local_path .. PATHSEP .. (repository.commit or repository.branch)) then - table.insert(repositories, repository:add(true)) - end - end - end -end - - -local 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 - table.remove(repositories, idx) - else - repo = Repository.url(url):add(AUTO_PULL_REMOTES and "recursive" or false) - end - table.insert(repositories, 1, repo) - repo:update() - end -end - - -local 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 -end - - -local function lpm_repo_update(...) - local t = { ... } - if #t == 0 then table.insert(t, false) end - for i, url in ipairs(t) do - local repo = url and get_repository(url) - for i,v in ipairs(repositories) do - if not repo or v == repo then - v:update(AUTO_PULL_REMOTES and "recursive" or false) - end - end - end -end - -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() - common.mkdirp(CACHEDIR .. PATHSEP .. "lite_xls") - common.write(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. "locals.json", - json.encode(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 } end)) - ) -end - -local function lpm_lite_xl_add(version, path) - if not version then error("requires a version") end - if not path then error("requires a path") end - if not system.stat(path .. PATHSEP .. "lite-xl") then error("can't find " .. path .. PATHSEP .. "lite-xl") end - if not system.stat(path .. PATHSEP .. "data") then error("can't find " .. path .. PATHSEP .. "data") end - table.insert(lite_xls, LiteXL.new(nil, { version = version, path = path:gsub(PATHSEP .. "$", ""), mod_version = MOD_VERSION or 3 })) - lpm_lite_xl_save() -end - -local 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() -end - -local 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) - if not version then error("requires a version") end - target = target or common.path("lite-xl") - if not target then error("can't find installed lite-xl. please provide a target to install the symlink explicitly") 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 - local stat = system.stat(target) - if stat and stat.symlink then os.remove(target) end - system.symlink(lite_xl:get_binary_path(), target) - if not common.path('lite-xl') then - os.remove(target) - error(target .. " is not on your $PATH; please supply a target that can be found on your $PATH, called `lite-xl`.") - end -end - - -local 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() - local result = { ["lite-xl"] = { } } - local max_version = 0 - for i,lite_xl in ipairs(lite_xls) do - table.insert(result["lite-xl"], { - version = lite_xl.version, - mod_version = lite_xl.mod_version, - tags = lite_xl.tags, - is_system = lite_xl:is_system(), - status = lite_xl:is_installed() and (lite_xl:is_local() and "local" or "installed") or "available", - local_path = lite_xl.local_path - }) - max_version = math.max(max_version, #lite_xl.version) - end - for i,repo in ipairs(repositories) do - if not repo.lite_xls then error("can't find lite-xl for repo " .. repo:url()) end - for j, lite_xl in ipairs(repo.lite_xls) do - table.insert(result["lite-xl"], { - version = lite_xl.version, - mod_version = lite_xl.mod_version, - repository = repo:url(), - tags = lite_xl.tags, - is_system = lite_xl:is_system(), - status = lite_xl:is_installed() and (lite_xl:is_local() and "local" or "installed") or "available", - local_path = lite_xl.local_path - }) - max_version = math.max(max_version, #lite_xl.version) - end - end - if JSON then - io.stdout:write(json.encode(result) .. "\n") - else - if VERBOSE then - for i, lite_xl in ipairs(result["lite-xl"]) do - if i ~= 0 then print("---------------------------") end - print("Version: " .. lite_xl.version) - print("Status: " .. lite_xl.status) - print("Mod-Version: " .. (lite_xl.mod_version or "unknown")) - print("Tags: " .. common.join(", ", lite_xl.tags)) - end - else - max_version = max_version + 2 - print(string.format("%" .. max_version .. "s | %10s | %s", "Version", "Status", "Location")) - print(string.format("%" .. max_version .."s | %10s | %s", "-------", "---------", "---------------------------")) - for i, lite_xl in ipairs(result["lite-xl"]) do - print(string.format("%" .. max_version .. "s | %10s | %s", (lite_xl.is_system and "* " or "") .. lite_xl.version, lite_xl.status, (lite_xl.status ~= "available" and lite_xl.local_path or lite_xl.repository))) - end - end - end -end - -local function lpm_lite_xl_run(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) - local plugins = {} - for i, str in ipairs({ ... }) do - local name, version = common.split(":", str) - local plugin = system_bottle:get_plugin(name, version, { mod_version = lite_xl.mod_version }) - if not plugin then error("can't find plugin " .. str) end - table.insert(plugins, plugin) - end - local bottle = Bottle.new(lite_xl, plugins) - if not bottle:is_constructed() then bottle:construct() end - bottle:run() -end - - -local function lpm_install(...) - for i, identifier in ipairs({ ... }) do - local s = identifier:find(":") - local name, version = (s and identifier:sub(1, s-1) or identifier), (s and identifier:sub(s+1) or nil) - if not name then error('unrecognized identifier ' .. identifier) end - if name == "lite-xl" then - lpm_lite_xl_install(version) - else - local plugins = { system_bottle:get_plugin(name, version, { mod_version = system_bottle.lite_xl.mod_version }) } - if #plugins == 0 then error("can't find plugin " .. name .. " mod-version: " .. (system_bottle.lite_xl.mod_version or 'any')) end - for j,v in ipairs(plugins) do v:install(system_bottle) end - end - end -end - - -local function lpm_plugin_uninstall(...) - for i, name in ipairs({ ... }) do - local plugins = { system_bottle:get_plugin(name) } - if #plugins == 0 then error("can't find plugin " .. name) end - local installed_plugins = common.grep(plugins, function(e) return e:is_installed(system_bottle) end) - if #installed_plugins == 0 then error("plugin " .. name .. " not installed") end - for i, plugin in ipairs(installed_plugins) do plugin:uninstall(system_bottle) end - end -end - -local function lpm_plugin_reinstall(...) for i, name in ipairs({ ... }) do pcall(lpm_plugin_uninstall, name) end lpm_install(...) end - -local 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 .. PATHSEP .. (repo.commit or repo.branch), remotes = common.map(repo.remotes or {}, function(r) return r:url() end) } end) }) .. "\n") - else - for i, repository in ipairs(repositories) do - local _, remotes = repository:parse_manifest() - if i ~= 0 then print("---------------------------") end - print("Remote : " .. repository:url()) - print("Path : " .. repository.local_path .. PATHSEP .. (repository.commit or repository.branch)) - print("Remotes: " .. json.encode(common.map(repository.remotes or {}, function(r) return r:url() end))) - end - end -end - -local function lpm_plugin_list() - local max_name = 0 - local result = { plugins = { } } - for j,plugin in ipairs(system_bottle:all_plugins()) do - max_name = math.max(max_name, #plugin.name) - local repo = plugin.repository - table.insert(result.plugins, { - name = plugin.name, - status = plugin.repository and (plugin:is_installed(system_bottle) and "installed" or (system_bottle.lite_xl:is_compatible(plugin) and "available" or "incompatible")) or (plugin:is_core(system_bottle) and "core" or "orphan"), - version = "" .. plugin.version, - dependencies = plugin.dependencies, - description = plugin.description, - mod_version = plugin.mod_version, - tags = plugin.tags, - type = plugin.type, - organization = plugin.organization, - repository = repo and repo:url() - }) - end - if JSON then - io.stdout:write(json.encode(result) .. "\n") - else - if not VERBOSE then - print(string.format("%" .. max_name .."s | %10s | %10s | %s", "Name", "Version", "ModVer", "Status")) - print(string.format("%" .. max_name .."s | %10s | %10s | %s", "--------------", "----------", "----------", "-----------")) - end - for i, plugin in ipairs(common.sort(result.plugins, function(a,b) return a.name < b.name end)) do - if VERBOSE then - if i ~= 0 then print("---------------------------") end - print("Name: " .. plugin.name) - print("Version: " .. plugin.version) - print("Status: " .. plugin.status) - print("Type: " .. plugin.type) - print("Orgnization: " .. plugin.organization) - print("Repository: " .. (plugin.repository or "orphan")) - print("Description: " .. (plugin.description or "")) - print("Mod-Version: " .. (plugin.mod_version or "unknown")) - print("Dependencies: " .. json.encode(plugin.dependencies)) - print("Tags: " .. common.join(", ", plugin.tags)) - elseif plugin.status ~= "incompatible" then - print(string.format("%" .. max_name .."s | %10s | %10s | %s", plugin.name, plugin.version, plugin.mod_version, plugin.status)) - end - end - end -end - -local function lpm_describe() - for i,v in ipairs(repositories) do - if #common.grep(DEFAULT_REPOS, function(r) return r:url() == v:url() end) == 0 then - io.stdout:write("lpm add " .. v:url() .. " && ") - end - end - print("lpm run " .. system_bottle.lite_xl.version .. " " .. common.join(" ", common.map(system_bottle:installed_plugins(), function(p) return p.name .. ":" .. p.version end))) -end - -local function lpm_plugin_upgrade() - for i,plugin in ipairs(system_bottle:installed_plugins()) do - local upgrade = common.sort(system_bottle:get_plugin(plugin.name, ">" .. plugin.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_purge() - -- local path = common.path("lite-xl") - -- if path then - -- local lite_xl = get_lite_xl("system") - -- if lite_xl then - -- os.remove(path) - -- system.symlink(lite_xl:get_binary_path(), target) - -- log_action("Reset lite-xl symlink to system.") - -- end - -- end - log_action("Removed " .. CACHEDIR .. ".") - common.rmrf(CACHEDIR) -end - -local function parse_arguments(arguments, options) - local args = {} - local i = 1 - while i <= #arguments do - local s,e, option, value = arguments[i]:find("%-%-([^=]+)=?(.*)") - if s then - local flag_type = options[option] - if not flag_type then error("unknown flag --" .. option) end - if flag_type == "flag" then - args[option] = true - elseif flag_type == "string" or flag_type == "number" then - if not value then - if i < #arguments then error("option " .. option .. " requires a " .. flag_type) end - value = arguments[i+1] - i = i + 1 - end - if flag_type == "number" and tonumber(flag_type) == nil then error("option " .. option .. " should be a number") end - args[option] = value - end - else - table.insert(args, arguments[i]) - end - i = i + 1 - end - return args -end - -local status = 0 -local function error_handler(err) - local s, e = err:find(":%d+") - 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() }) .. "\n") - else - io.stderr:write(json.encode({ error = message or err, actions = actions, warnings = warnings }) .. "\n") - end - else - io.stderr:write((not VERBOSE and message or err) .. "\n") - if VERBOSE then io.stderr:write(debug.traceback() .. "\n") end - end - status = -1 -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" and ARGS[3] == "install" then lpm_install(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "plugin" and ARGS[3] == "uninstall" then lpm_plugin_uninstall(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "plugin" and ARGS[3] == "reinstall" then lpm_plugin_reinstall(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "plugin" and (#ARGS == 2 or ARGS[3] == "list") then return lpm_plugin_list(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "plugin" and ARGS[3] == "upgrade" then return lpm_plugin_upgrade(table.unpack(common.slice(ARGS, 4))) - elseif ARGS[2] == "upgrade" then return lpm_plugin_upgrade(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "install" then lpm_install(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "uninstall" then lpm_plugin_uninstall(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "reinstall" then lpm_plugin_reinstall(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "describe" then lpm_describe(table.unpack(common.slice(ARGS, 3))) - elseif ARGS[2] == "list" then return lpm_plugin_list(table.unpack(common.slice(ARGS, 3))) - 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] == "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 })) - end -end - - -xpcall(function() - local ARGS = parse_arguments(ARGV, { - json = "flag", userdir = "string", cachedir = "string", version = "flag", verbose = "flag", - quiet = "flag", version = "string", ["mod-version"] = "string", remotes = "flag", help = "flag", - remotes = "flag", ssl_certs = "string", force = "flag", arch = "string", ["assume-yes"] = "flag", - ["install-optional"] = "flag" - }) - if ARGS["version"] then - io.stdout:write(VERSION .. "\n") - return 0 - end - if ARGS["help"] or #ARGS == 1 or ARGS[2] == "help" then - io.stderr:write([[ -Usage: lpm COMMAND [...ARGUMENTS] [--json] [--userdir=directory] - [--cachedir=directory] [--quiet] [--version] [--help] [--remotes] - [--ssl_certs=directory/file] [--force] [--arch=]] .. _G.ARCH .. [[] - [--assume-yes] [--no-install-optional] [--verbose] [--mod-version=3] - -LPM is a package manager for `lite-xl`, written in C (and packed-in lua). - -It's designed to install packages from our central github repository (and -affiliated repositories), directly into your lite-xl user directory. It can -be called independently, for from the lite-xl `plugin_manager` plugin. - -LPM will always use https://github.com/lite-xl/lite-xl-plugins as its base -repository, if none are present, and the cache directory does't exist, -but others can be added, and this base one can be removed. - -It has the following commands: - - lpm init Implicitly called before all commands - if necessary, but can be called - independently to save time later. - lpm repo list List all extant repos. - lpm [repo] add <repository remote> Add a source repository. - [...<repository remote>] - lpm [repo] rm <repository remote> Remove a source repository. - [...<repository remote>] - lpm [repo] update [<repository remote>] Update all/the specified repos. - [...<repository remote>] - lpm [plugin] install Install specific plugins. - <plugin name>[:<version>] If installed, upgrades. - [...<plugin name>:<version>] - lpm [plugin] uninstall <plugin name> Uninstall the specific plugin. - [...<plugin name>] - lpm [plugin] reinstall <plugin name> Uninstall and installs the specific plugin. - [...<plugin name>] - lpm [plugin] list <repository remote> List all/associated plugins. - [...<repository remote>] - lpm [plugin] upgrade Upgrades all installed plugins - to new version if applicable. - lpm [lite-xl] install <version> Installs lite-xl. Infers the - [binary] [datadir] paths on your system if not - supplied. Automatically - switches to be your system default - if path auto inferred. - lpm lite-xl add <version> <path> Adds a local version of lite-xl to - the managed list, allowing it to be - easily bottled. - lpm lite-xl remove <path> Removes a local version of lite-xl - from the managed list. - lpm [lite-xl] switch <version> [<path>] Sets the active version of lite-xl - to be the specified version. Auto-detects - current install of lite-xl; if none found - path can be specifeid. - lpm lite-xl list Lists all installed versions of - lite-xl. - lpm run <version> [...plugins] Sets up a "bottle" to run the specified - lite version, with the specified plugins - and then opens it. - lpm describe [bottle] Describes the bottle specified in the form - of a list of commands, that allow someone - else to run your configuration. - - lpm purge Completely purge all state for LPM. - lpm - Read these commands from stdin in - an interactive print-eval loop. - lpm help Displays this help text. - -Flags have the following effects: - - --json Performs all communication in JSON. - --userdir=directory Sets the lite-xl userdir manually. - If omitted, uses the normal lite-xl logic. - --cachedir=directory Sets the directory to store all repositories. - --tmpdir=directory During install, sets the staging area. - --verbose Spits out more information, including intermediate - steps to install and whatnot. - --quiet Outputs nothing but explicit responses. - --mod-version Sets the mod version of lite-xl to install plugins. - --version Returns version information. - --remotes Automatically adds any specified remotes in the - repository to the end of the resolution list. - This is a potential security risk, so be careful. - --help Displays this help text. - --ssl_certs Sets the SSL certificate store. - --arch Sets the architecture (default: ]] .. _G.ARCH .. [[). - --force Ignores checksum inconsitencies. - Not recommended; security risk. - --assume-yes Ignores any prompts, and automatically answers yes - to all. - --no-install-optional On install, anything marked as optional - won't prompt. -]] - ) - return 0 - end - - VERBOSE = ARGS["verbose"] or false - JSON = ARGS["json"] or os.getenv("LPM_JSON") - QUIET = ARGS["quiet"] or os.getenv("LPM_QUIET") - FORCE = ARGS["force"] - NO_INSTALL_OPTIONAL = ARGS["no-install-optional"] - ARCH = ARGS["arch"] or _G.ARCH - ASSUME_YES = ARGS["assume-yes"] or FORCE - MOD_VERSION = ARGS["mod-version"] or os.getenv("LPM_MODVERSION") - if MOD_VERSION == "any" then MOD_VERSION = nil end - HOME = (os.getenv("USERPROFILE") or os.getenv("HOME")):gsub(PATHSEP .. "$", "") - USERDIR = ARGS["userdir"] or os.getenv("LITE_USERDIR") or (os.getenv("XDG_CONFIG_HOME") and os.getenv("XDG_CONFIG_HOME") .. PATHSEP .. "lite-xl") - or (HOME and (HOME .. PATHSEP .. '.config' .. PATHSEP .. 'lite-xl')) - AUTO_PULL_REMOTES = ARGS["remotes"] - if not system.stat(USERDIR) then error("can't find user directory " .. USERDIR) end - CACHEDIR = ARGS["cachedir"] or os.getenv("LPM_CACHE") or USERDIR .. PATHSEP .. "lpm" - TMPDIR = ARGS["tmpdir"] or CACHEDIR .. "/tmp" - - repositories = {} - if ARGS[2] == "purge" then return lpm_purge() end - if ARGS["ssl_certs"] then - local stat = system.stat(ARGS["ssl_certs"]) - if not stat then error("can't find " .. ARGS["ssl_certs"]) end - system.certs(stat.type, ARGS["ssl_certs"]) - elseif not os.getenv("SSL_CERT_DIR") and not os.getenv("SSL_CERT_FILE") then - local paths = { -- https://serverfault.com/questions/62496/ssl-certificate-location-on-unix-linux#comment1155804_62500 - "/etc/ssl/certs/ca-certificates.crt", -- Debian/Ubuntu/Gentoo etc. - "/etc/pki/tls/certs/ca-bundle.crt", -- Fedora/RHEL 6 - "/etc/ssl/ca-bundle.pem", -- OpenSUSE - "/etc/pki/tls/cacert.pem", -- OpenELEC - "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", -- CentOS/RHEL 7 - "/etc/ssl/cert.pem", -- Alpine Linux - "/etc/ssl/certs", -- SLES10/SLES11, https://golang.org/issue/12139 - "/system/etc/security/cacerts", -- Android - "/usr/local/share/certs", -- FreeBSD - "/etc/pki/tls/certs", -- Fedora/RHEL - "/etc/openssl/certs", -- NetBSD - "/var/ssl/certs", -- AIX - } - for i, path in ipairs(paths) do - local stat = system.stat(path) - if stat then - system.certs(stat.type, path) - break - end - end - end - - - -- 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. - lpm_repo_init() - repositories = {} - for i, remote_hash in ipairs(system.ls(CACHEDIR .. PATHSEP .. "repos")) do - local remote - for j, commit_or_branch in ipairs(system.ls(CACHEDIR .. PATHSEP .. "repos" .. PATHSEP .. remote_hash)) do - if system.stat(CACHEDIR .. PATHSEP .. "repos" .. PATHSEP .. remote_hash .. PATHSEP .. commit_or_branch .. PATHSEP .. ".git" .. PATHSEP .."config") then - for line in io.lines(CACHEDIR .. PATHSEP .. "repos" .. PATHSEP .. remote_hash .. PATHSEP .. commit_or_branch .. PATHSEP .. ".git" .. PATHSEP .."config") do - local s,e = line:find("url = ") - if s then remote = line:sub(e+1) break end - end - if remote then - table.insert(repositories, Repository.url(remote .. ":" .. commit_or_branch)) - repositories[#repositories]:parse_manifest() - end - end - end - end - - lite_xls = {} - if system.stat(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. "locals.json") then - for i, lite_xl in ipairs(json.decode(io.open(CACHEDIR .. PATHSEP .. "lite_xls" .. PATHSEP .. "locals.json", "rb"):read("*all"))) do - table.insert(lite_xls, LiteXL.new(nil, { version = lite_xl.version, mod_version = lite_xl.mod_version, path = lite_xl.path, tags = { "local" } })) - end - end - local lite_xl_binary = common.path("lite-xl") - if lite_xl_binary then - lite_xl_binary = system.stat(lite_xl_binary).symlink or lite_xl_binary - local directory = common.dirname(lite_xl_binary) - local hash = system.hash(lite_xl_binary, "file") - local system_lite_xl = common.first(common.concat(common.flat_map(repositories, function(r) return r.lite_xls end), lite_xls), function(lite_xl) return lite_xl.local_path == directory end) - if not system_lite_xl then - if #common.grep(lite_xls, function(e) return e.version == "system" end) > 0 then error("can't create new system lite, please `lpm rm lite-xl system`, or resolve otherwise") end - system_lite_xl = LiteXL.new(nil, { path = directory, mod_version = 3, version = "system", tags = { "system", "local" } }) - table.insert(lite_xls, system_lite_xl) - lpm_lite_xl_save() - else - table.insert(system_lite_xl.tags, "system") - end - system_bottle = Bottle.new(system_lite_xl, nil, true) - end - if not system_bottle then system_bottle = Bottle.new(nil, nil, true) end - - if ARGS[2] ~= '-' then - run_command(ARGS) - else - while true do - local line = io.stdin:read("*line") - if line == "quit" or line == "exit" then return 0 end - local args = { ARGS[1] } - local s = 1 - while true do - local a,e = line:find("%s+", s) - table.insert(args, line:sub(s, a and (a - 1) or #line)) - if not e then break end - s = e + 1 - end - xpcall(function() - run_command(args) - end, error_handler) - actions, warnings = {}, {} - end - end - -end, error_handler) - - -return status |