mirror of
https://github.com/CCLeonOS/LeonOS.git
synced 2026-03-03 06:47:00 +00:00
feat: 初始提交 LeonOS 实现
添加 LeonOS 的基本实现,包括: - 核心 API 模块(colors, disk, gps, keys, multishell, parallel, rednet, redstone, settings, vector) - 命令行程序(about, alias, bg, clear, copy, delete, edit, fg, help, list, lua, mkdir, move, paint, peripherals, programs, reboot, set, shutdown, threads) - 系统启动脚本和包管理 - 文档(README.md, LICENSE) - 开发工具(devbin)和更新程序 实现功能: - 完整的线程管理系统 - 兼容 ComputerCraft 的 API 设计 - 改进的 shell 和命令补全系统 - 多标签终端支持 - 设置管理系统
This commit is contained in:
293
data/computercraft/lua/rom/modules/main/cc/http/gist.lua
Normal file
293
data/computercraft/lua/rom/modules/main/cc/http/gist.lua
Normal file
@@ -0,0 +1,293 @@
|
||||
--- gist.lua - Gist client for ComputerCraft
|
||||
-- Made by JackMacWindows for LeonOS-PC and CC: Tweaked
|
||||
--
|
||||
-- @module cc.http.gist
|
||||
|
||||
local textutils = require("textutils")
|
||||
local settings = require("settings")
|
||||
local expect = require("cc.expect").expect
|
||||
local colors = require("colors")
|
||||
local write = require("rc").write
|
||||
local term = require("term")
|
||||
local http = require("http")
|
||||
local read = term.read
|
||||
|
||||
local gist = {}
|
||||
|
||||
local function emptyfn() end -- to reduce memory/speed footprint when using empty functions
|
||||
|
||||
-- Internal functions
|
||||
|
||||
local function getGistFile(data)
|
||||
if not data.truncated then return data.content else
|
||||
local handle = http.get(data.raw_url)
|
||||
if not handle then error("Could not connect to api.github.com.") end
|
||||
if handle.getResponseCode() ~= 200 then
|
||||
handle.close()
|
||||
error("Failed to download file data.")
|
||||
end
|
||||
local d = handle.readAll()
|
||||
handle.close()
|
||||
return d
|
||||
end
|
||||
end
|
||||
|
||||
local function setTextColor(c) if term.isColor() then term.setTextColor(c) elseif c == colors.white or c == colors.yellow then term.setTextColor(colors.white) else term.setTextColor(colors.lightGray) end end
|
||||
|
||||
local function requestAuth(headers, interactive)
|
||||
if settings.get("gist.id") ~= nil then
|
||||
headers.Authorization = "token " .. settings.get("gist.id")
|
||||
return true
|
||||
elseif interactive then
|
||||
setTextColor(colors.yellow)
|
||||
write("You need to add a Personal Access Token (PAK) to upload Gists. Follow the instructions at ")
|
||||
setTextColor(colors.blue)
|
||||
write("https://tinyurl.com/GitHubPAK")
|
||||
setTextColor(colors.yellow)
|
||||
write(" to generate one. Make sure to check the '")
|
||||
setTextColor(colors.blue)
|
||||
write("gist")
|
||||
setTextColor(colors.yellow)
|
||||
print("' checkbox on step 7 (under 'Select scopes'). Once done, paste it here.")
|
||||
setTextColor(colors.lime)
|
||||
write("PAK: ")
|
||||
setTextColor(colors.white)
|
||||
local pak = read()
|
||||
if pak == nil or pak == "" then error("Invalid PAK, please try again.") end
|
||||
settings.set("gist.id", pak)
|
||||
settings.save(".settings")
|
||||
headers.Authorization = "token " .. pak
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- User API - this can be loaded with require "cc.http.gist"
|
||||
|
||||
-- ID can be either just the gist ID or a gist ID followed by a slash and a file name
|
||||
-- (This also includes Gist URLs)
|
||||
-- * If a file name is specified, retrieves that file
|
||||
-- * Otherwise, if there's only one file, retrieves that file
|
||||
-- * Otherwise, if there's a file named 'init.lua', retrieves 'init.lua'
|
||||
-- * Otherwise, if there's more than one file but only one *.lua file, retrieves the Lua file
|
||||
-- * Otherwise, retrieves the first Lua file alphabetically (with a warning)
|
||||
-- * Otherwise, fails
|
||||
|
||||
--- Retrieves one file from a Gist using the specified ID.
|
||||
-- @tparam string id The Gist ID to download from. See above comments for more details.
|
||||
-- @tparam[opt] function progress A function to use to report status messages.
|
||||
-- @treturn string|nil The contents of the specified Gist file, or nil on error.
|
||||
-- @treturn string|nil The name of the file that was chosen to be downloaded, or a message on error.
|
||||
function gist.get(id, progress)
|
||||
expect(1, id, "string")
|
||||
expect(2, progress, "function", "nil")
|
||||
progress = progress or emptyfn
|
||||
local file
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id, file = id:match("^([0-9A-Fa-f:]+)/(.+)$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'get' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
progress("Connecting to api.github.com... ")
|
||||
local handle = http.get("https://api.github.com/gists/" .. id)
|
||||
if handle == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, "Failed to connect"
|
||||
end
|
||||
local meta = textutils.unserializeJSON(handle.readAll())
|
||||
local code = handle.getResponseCode()
|
||||
handle.close()
|
||||
if code ~= 200 then
|
||||
progress("Failed.\n")
|
||||
return nil, "Invalid response code (" .. code .. ")" .. (meta and ": " .. meta.message or "")
|
||||
end
|
||||
if meta == nil or meta.files == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, meta and "GitHub API error: " .. meta.message or "Error parsing JSON"
|
||||
end
|
||||
progress("Success.\n")
|
||||
if file then return getGistFile(meta.files[file]), file
|
||||
elseif next(meta.files, next(meta.files)) == nil then return getGistFile(meta.files[next(meta.files)]), next(meta.files)
|
||||
elseif meta.files["init.lua"] ~= nil then return getGistFile(meta.files["init.lua"]), "init.lua"
|
||||
else
|
||||
local luaFiles = {}
|
||||
for k in pairs(meta.files) do if k:match("%.lua$") then table.insert(luaFiles, k) end end
|
||||
table.sort(luaFiles)
|
||||
if #luaFiles == 0 then
|
||||
progress("Error: Could not find any Lua files to download!\n")
|
||||
return nil, "Could not find any Lua files to download"
|
||||
end
|
||||
if #luaFiles > 1 then progress("Warning: More than one Lua file detected, downloading the first one alphabetically.\n") end
|
||||
return getGistFile(meta.files[luaFiles[1]]), luaFiles[1]
|
||||
end
|
||||
end
|
||||
|
||||
--- Runs a specified Gist. This is a wrapper for convenience.
|
||||
-- @tparam string id The Gist ID to download from. See above comments for more details.
|
||||
-- @tparam[opt] function progress A function to use to report status messages. If
|
||||
-- this is not a function, it will be used as an argument to the script.
|
||||
-- @tparam[opt] any ... Any arguments to pass to the script.
|
||||
-- @treturn any Any results returned from the script.
|
||||
function gist.run(id, progress, ...)
|
||||
expect(1, id, "string")
|
||||
local args = table.pack(...)
|
||||
if type(progress) ~= "function" and progress ~= nil then
|
||||
table.insert(args, 1, progress)
|
||||
progress = nil
|
||||
end
|
||||
local data, name = gist.get(id, progress)
|
||||
if data == nil then return end
|
||||
local fn, err = load(data, name, "t", _ENV)
|
||||
if fn == nil then error(err) end
|
||||
local retval = table.pack(pcall(fn, table.unpack(args)))
|
||||
if not retval[1] then error(retval[2]) end
|
||||
return table.unpack(retval, 2)
|
||||
end
|
||||
|
||||
--- Retrieves a table of all files from a Gist.
|
||||
-- @tparam string id The Gist ID to download.
|
||||
-- @tparam[opt] function progress A function to use to report status messages.
|
||||
-- @treturn table|nil A key-value list of all files in the Gist, or nil on error.
|
||||
-- @treturn string|nil If an error occurred, a string describing the error.
|
||||
function gist.getAll(id, progress)
|
||||
expect(1, id, "string")
|
||||
expect(2, progress, "function", "nil")
|
||||
progress = progress or emptyfn
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id = id:match("^([0-9A-Fa-f:]+)/.*$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'getAll' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
progress("Connecting to api.github.com... ")
|
||||
local handle = http.get("https://api.github.com/gists/" .. id)
|
||||
if handle == nil then progress("Failed.\n") return nil, "Failed to connect" end
|
||||
local meta = textutils.unserializeJSON(handle.readAll())
|
||||
local code = handle.getResponseCode()
|
||||
handle.close()
|
||||
if code ~= 200 then
|
||||
progress("Failed.\n")
|
||||
return nil, "Invalid response code (" .. code .. ")" .. (meta and ": " .. meta.message or "")
|
||||
end
|
||||
if meta == nil or meta.files == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, meta and meta.message and "GitHub API error: " .. meta.message or "Error parsing JSON"
|
||||
end
|
||||
progress("Success.\n")
|
||||
local retval = {}
|
||||
for k, v in pairs(meta.files) do retval[k] = getGistFile(v) end
|
||||
return retval
|
||||
end
|
||||
|
||||
--- Returns some information about a Gist.
|
||||
-- @tparam string id The Gist ID to get info about.
|
||||
-- @tparam[opt] function progress A function to use to report status messages.
|
||||
-- @treturn table|nil A table of information about the Gist. The table may
|
||||
-- contain the following entries:
|
||||
-- - description: The description for the Gist.
|
||||
-- - author: The username of the author of the Gist.
|
||||
-- - revisionCount: The number of revisions that have been made to the Gist.
|
||||
-- - files: A list of all file names in the Gist, sorted alphabetically.
|
||||
-- @treturn string|nil If an error occurred, a string describing the error.
|
||||
function gist.info(id, progress)
|
||||
expect(1, id, "string")
|
||||
expect(2, progress, "function", "nil")
|
||||
progress = progress or emptyfn
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id = id:match("^([0-9A-Fa-f:]+)/.*$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'info' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
progress("Connecting to api.github.com... ")
|
||||
local handle = http.get("https://api.github.com/gists/" .. id)
|
||||
if handle == nil then progress("Failed.\n") return nil, "Failed to connect" end
|
||||
local meta = textutils.unserializeJSON(handle.readAll())
|
||||
local code = handle.getResponseCode()
|
||||
handle.close()
|
||||
if code ~= 200 then
|
||||
progress("Failed.\n")
|
||||
return nil, "Invalid response code (" .. code .. ")" .. (meta and ": " .. meta.message or "")
|
||||
end
|
||||
if meta == nil or meta.files == nil then
|
||||
progress("Failed.\n")
|
||||
return nil, meta and meta.message and "GitHub API error: " .. meta.message or "Error parsing JSON"
|
||||
end
|
||||
local f = {}
|
||||
for k in pairs(meta.files) do table.insert(f, k) end
|
||||
table.sort(f)
|
||||
progress("Success.\n")
|
||||
return { description = meta.description, author = meta.owner.login, revisionCount = #meta.history, files = f }
|
||||
end
|
||||
|
||||
--- Uploads a list of files to Gist, updating a previous Gist if desired.
|
||||
-- @tparam table files The files to upload to Gist. This table should be
|
||||
-- structured with a key as file name and a string with the file contents. If
|
||||
-- updating a Gist, files can be deleted by setting the data to textutils.json_null.
|
||||
-- @tparam[opt] string description The description of the Gist. This is required
|
||||
-- when updating a Gist, but is optional when uploading a Gist for the first
|
||||
-- time. If you don't want to change the description when updating, you can get
|
||||
-- the current description with gist.info() and pass in the description field.
|
||||
-- @tparam[opt] string id The ID of the Gist to update. If nil, a new Gist will
|
||||
-- be created.
|
||||
-- @tparam[opt] boolean interactive Set this to true to allow asking for a PAK
|
||||
-- if one is not available in the settings. If this is not specified, this
|
||||
-- function will return nil if gist.id is not available in the settings.
|
||||
-- @treturn string|nil The ID of the Gist, or nil on error.
|
||||
-- @treturn string|nil The URL of the Gist, or a string on error.
|
||||
function gist.put(files, description, id, interactive)
|
||||
expect(1, files, "table")
|
||||
expect(3, id, "string", "nil")
|
||||
expect(2, description, "string", id == nil and "nil" or nil)
|
||||
expect(4, interactive, "boolean", "nil")
|
||||
if id then
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil then id = id:match("^([0-9A-Fa-f:]+)/.*$") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #3 to 'put' (invalid ID)", 2) end
|
||||
if id:find(":") ~= nil then id = id:gsub(":", "/") end
|
||||
end
|
||||
local data = { files = {}, public = true, description = description }
|
||||
for k, v in pairs(files) do if v == textutils.json_null then data.files[k] = v else data.files[k] = { content = v } end end
|
||||
local headers = { ["Content-Type"] = "application/json" }
|
||||
if not requestAuth(headers, interactive) then return nil, "Authentication required" end
|
||||
if interactive then write("Connecting to api.github.com... ") end
|
||||
local handle
|
||||
if id then handle = http.post{ url = "https://api.github.com/gists/" .. id, body = textutils.serializeJSON(data):gsub("\n", "n"), headers = headers, method = "PATCH" }
|
||||
else handle = http.post("https://api.github.com/gists", textutils.serializeJSON(data):gsub("\n", "n"), headers) end
|
||||
if handle == nil then if interactive then print("Failed.") end return nil, "Could not connect" end
|
||||
local resp = textutils.unserializeJSON(handle.readAll())
|
||||
if handle.getResponseCode() ~= 201 and handle.getResponseCode() ~= 200 or resp == nil then
|
||||
if interactive then print("Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")) end
|
||||
handle.close()
|
||||
return nil, "Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")
|
||||
end
|
||||
handle.close()
|
||||
if interactive then print("Success.") end
|
||||
return resp.id, resp.html_url
|
||||
end
|
||||
|
||||
--- Deletes a Gist.
|
||||
-- @tparam string id The Gist ID to delete.
|
||||
-- @tparam[opt] boolean interactive Set this to true to allow asking for a PAK
|
||||
-- if one is not available in the settings. If this is not specified, this
|
||||
-- function will return false if gist.id is not available in the settings.
|
||||
-- @treturn boolean Whether the request succeeded.
|
||||
-- @treturn string|nil If an error occurred, a message describing the error.
|
||||
function gist.delete(id, interactive)
|
||||
expect(1, id, "string")
|
||||
expect(2, interactive, "boolean", "nil")
|
||||
if id:find("https?://") then id = id:gsub("https?://[^/]+/", ""):gsub("^[^/]*[^/%x]+[^/]*/", "") end
|
||||
if id:find("/") ~= nil or id:find(":") ~= nil then id = id:match("^([0-9A-Fa-f]+)") end
|
||||
if id == nil or not id:match("^[0-9A-Fa-f][0-9A-Fa-f:]+[0-9A-Fa-f]$") then error("bad argument #1 to 'delete' (invalid ID)", 2) end
|
||||
local headers = {}
|
||||
if not requestAuth(headers, interactive) then return false, "Authentication required" end
|
||||
if interactive then write("Connecting to api.github.com... ") end
|
||||
local handle = http.post{ url = "https://api.github.com/gists/" .. id, headers = headers, method = "DELETE" }
|
||||
if handle == nil then if interactive then print("Failed.") end return false, "Could not connect" end
|
||||
if handle.getResponseCode() ~= 204 then
|
||||
local resp = textutils.unserializeJSON(handle.readAll())
|
||||
if interactive then print("Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")) end
|
||||
handle.close()
|
||||
return false, "Failed: " .. handle.getResponseCode() .. ": " .. (resp and resp.message or "Unknown error")
|
||||
end
|
||||
handle.close()
|
||||
if interactive then print("Success.") end
|
||||
return true
|
||||
end
|
||||
|
||||
return gist
|
||||
Reference in New Issue
Block a user