From ee2670d53b2b63b7167cccd70d75bbd89b37b0f7 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 21 Nov 2022 22:47:07 +0000 Subject: [PATCH] Move some functions out of bios into their own APIs This removes the patching of fs and http, and replaces them with their own standard Lua APIs. This makes the bios a little simpler, and means we can move the documentation in line. --- doc/stub/fs.lua | 64 ---- doc/stub/http.lua | 177 ---------- .../resources/data/computercraft/lua/bios.lua | 260 +-------------- .../data/computercraft/lua/rom/apis/fs.lua | 147 +++++++++ .../computercraft/lua/rom/apis/http/http.lua | 303 ++++++++++++++++++ .../lua/rom/apis/turtle/turtle.lua | 2 +- projects/forge/build.gradle.kts | 1 + 7 files changed, 467 insertions(+), 487 deletions(-) delete mode 100644 doc/stub/fs.lua delete mode 100644 doc/stub/http.lua create mode 100644 projects/core/src/main/resources/data/computercraft/lua/rom/apis/fs.lua create mode 100644 projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua diff --git a/doc/stub/fs.lua b/doc/stub/fs.lua deleted file mode 100644 index a393d7cd2..000000000 --- a/doc/stub/fs.lua +++ /dev/null @@ -1,64 +0,0 @@ ---- @module fs - ---- Returns true if a path is mounted to the parent filesystem. --- --- The root filesystem "/" is considered a mount, along with disk folders and --- the rom folder. Other programs (such as network shares) can exstend this to --- make other mount types by correctly assigning their return value for getDrive. --- --- @tparam string path The path to check. --- @treturn boolean If the path is mounted, rather than a normal file/folder. --- @throws If the path does not exist. --- @see getDrive --- @since 1.87.0 -function isDriveRoot(path) end - ---[[- Provides completion for a file or directory name, suitable for use with -@{_G.read}. - -When a directory is a possible candidate for completion, two entries are -included - one with a trailing slash (indicating that entries within this -directory exist) and one without it (meaning this entry is an immediate -completion candidate). `include_dirs` can be set to @{false} to only include -those with a trailing slash. - -@tparam[1] string path The path to complete. -@tparam[1] string location The location where paths are resolved from. -@tparam[1,opt=true] boolean include_files When @{false}, only directories will -be included in the returned list. -@tparam[1,opt=true] boolean include_dirs When @{false}, "raw" directories will -not be included in the returned list. - -@tparam[2] string path The path to complete. -@tparam[2] string location The location where paths are resolved from. -@tparam[2] { - include_dirs? = boolean, include_files? = boolean, - include_hidden? = boolean -} options -This table form is an expanded version of the previous syntax. The -`include_files` and `include_dirs` arguments from above are passed in as fields. - -This table also accepts the following options: - - `include_hidden`: Whether to include hidden files (those starting with `.`) - by default. They will still be shown when typing a `.`. - -@treturn { string... } A list of possible completion candidates. -@since 1.74 -@changed 1.101.0 -@usage Complete files in the root directory. - - read(nil, nil, function(str) - return fs.complete(str, "", true, false) - end) - -@usage Complete files in the root directory, hiding hidden files by default. - - read(nil, nil, function(str) - return fs.complete(str, "", { - include_files = true, - include_dirs = false, - included_hidden = false, - }) - end) -]] -function complete(path, location, include_files, include_dirs) end diff --git a/doc/stub/http.lua b/doc/stub/http.lua deleted file mode 100644 index 6ec4db8d3..000000000 --- a/doc/stub/http.lua +++ /dev/null @@ -1,177 +0,0 @@ ---- Make HTTP requests, sending and receiving data to a remote web server. --- --- @module http --- @since 1.1 --- @see local_ips To allow accessing servers running on your local network. - ---- Asynchronously make a HTTP request to the given url. --- --- This returns immediately, a @{http_success} or @{http_failure} will be queued --- once the request has completed. --- --- @tparam string url The url to request --- @tparam[opt] string body An optional string containing the body of the --- request. If specified, a `POST` request will be made instead. --- @tparam[opt] { [string] = string } headers Additional headers to send as part --- of this request. --- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true, --- the body will not be UTF-8 encoded, and the received response will not be --- decoded. --- --- @tparam[2] { --- url = string, body? = string, headers? = { [string] = string }, --- binary? = boolean, method? = string, redirect? = boolean, --- } request Options for the request. --- --- This table form is an expanded version of the previous syntax. All arguments --- from above are passed in as fields instead (for instance, --- `http.request("https://example.com")` becomes `http.request { url = --- "https://example.com" }`). --- --- This table also accepts several additional options: --- --- - `method`: Which HTTP method to use, for instance `"PATCH"` or `"DELETE"`. --- - `redirect`: Whether to follow HTTP redirects. Defaults to true. --- --- @see http.get For a synchronous way to make GET requests. --- @see http.post For a synchronous way to make POST requests. --- --- @changed 1.63 Added argument for headers. --- @changed 1.80pr1 Added argument for binary handles. --- @changed 1.80pr1.6 Added support for table argument. --- @changed 1.86.0 Added PATCH and TRACE methods. -function request(...) end - ---- Make a HTTP GET request to the given url. --- --- @tparam string url The url to request --- @tparam[opt] { [string] = string } headers Additional headers to send as part --- of this request. --- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true, --- the body will not be UTF-8 encoded, and the received response will not be --- decoded. --- --- @tparam[2] { --- url = string, headers? = { [string] = string }, --- binary? = boolean, method? = string, redirect? = boolean, --- } request Options for the request. See @{http.request} for details on how --- these options behave. --- --- @treturn Response The resulting http response, which can be read from. --- @treturn[2] nil When the http request failed, such as in the event of a 404 --- error or connection timeout. --- @treturn string A message detailing why the request failed. --- @treturn Response|nil The failing http response, if available. --- --- @changed 1.63 Added argument for headers. --- @changed 1.80pr1 Response handles are now returned on error if available. --- @changed 1.80pr1 Added argument for binary handles. --- @changed 1.80pr1.6 Added support for table argument. --- @changed 1.86.0 Added PATCH and TRACE methods. --- --- @usage Make a request to [example.tweaked.cc](https://example.tweaked.cc), --- and print the returned page. --- ```lua --- local request = http.get("https://example.tweaked.cc") --- print(request.readAll()) --- -- => HTTP is working! --- request.close() --- ``` -function get(...) end - ---- Make a HTTP POST request to the given url. --- --- @tparam string url The url to request --- @tparam string body The body of the POST request. --- @tparam[opt] { [string] = string } headers Additional headers to send as part --- of this request. --- @tparam[opt] boolean binary Whether to make a binary HTTP request. If true, --- the body will not be UTF-8 encoded, and the received response will not be --- decoded. --- --- @tparam[2] { --- url = string, body? = string, headers? = { [string] = string }, --- binary? = boolean, method? = string, redirect? = boolean, --- } request Options for the request. See @{http.request} for details on how --- these options behave. --- --- @treturn Response The resulting http response, which can be read from. --- @treturn[2] nil When the http request failed, such as in the event of a 404 --- error or connection timeout. --- @treturn string A message detailing why the request failed. --- @treturn Response|nil The failing http response, if available. --- --- @since 1.31 --- @changed 1.63 Added argument for headers. --- @changed 1.80pr1 Response handles are now returned on error if available. --- @changed 1.80pr1 Added argument for binary handles. --- @changed 1.80pr1.6 Added support for table argument. --- @changed 1.86.0 Added PATCH and TRACE methods. -function post(...) end - ---- Asynchronously determine whether a URL can be requested. --- --- If this returns `true`, one should also listen for @{http_check} which will --- container further information about whether the URL is allowed or not. --- --- @tparam string url The URL to check. --- @treturn true When this url is not invalid. This does not imply that it is --- allowed - see the comment above. --- @treturn[2] false When this url is invalid. --- @treturn string A reason why this URL is not valid (for instance, if it is --- malformed, or blocked). --- --- @see http.checkURL For a synchronous version. -function checkURLAsync(url) end - ---- Determine whether a URL can be requested. --- --- If this returns `true`, one should also listen for @{http_check} which will --- container further information about whether the URL is allowed or not. --- --- @tparam string url The URL to check. --- @treturn true When this url is valid and can be requested via @{http.request}. --- @treturn[2] false When this url is invalid. --- @treturn string A reason why this URL is not valid (for instance, if it is --- malformed, or blocked). --- --- @see http.checkURLAsync For an asynchronous version. --- --- @usage --- ```lua --- print(http.checkURL("https://example.tweaked.cc/")) --- -- => true --- print(http.checkURL("http://localhost/")) --- -- => false Domain not permitted --- print(http.checkURL("not a url")) --- -- => false URL malformed --- ``` -function checkURL(url) end - ---- Open a websocket. --- --- @tparam string url The websocket url to connect to. This should have the --- `ws://` or `wss://` protocol. --- @tparam[opt] { [string] = string } headers Additional headers to send as part --- of the initial websocket connection. --- --- @treturn Websocket The websocket connection. --- @treturn[2] false If the websocket connection failed. --- @treturn string An error message describing why the connection failed. --- @since 1.80pr1.1 --- @changed 1.80pr1.3 No longer asynchronous. --- @changed 1.95.3 Added User-Agent to default headers. -function websocket(url, headers) end - ---- Asynchronously open a websocket. --- --- This returns immediately, a @{websocket_success} or @{websocket_failure} --- will be queued once the request has completed. --- --- @tparam string url The websocket url to connect to. This should have the --- `ws://` or `wss://` protocol. --- @tparam[opt] { [string] = string } headers Additional headers to send as part --- of the initial websocket connection. --- @since 1.80pr1.3 --- @changed 1.95.3 Added User-Agent to default headers. -function websocketAsync(url, headers) end diff --git a/projects/core/src/main/resources/data/computercraft/lua/bios.lua b/projects/core/src/main/resources/data/computercraft/lua/bios.lua index a653c97a6..175d1dfbc 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/bios.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/bios.lua @@ -3,7 +3,7 @@ -- Ideally we'd use require, but that is part of the shell, and so is not -- available to the BIOS or any APIs. All APIs load this using dofile, but that -- has not been defined at this point. -local expect, field +local expect do local h = fs.open("rom/modules/main/cc/expect.lua", "r") @@ -11,8 +11,7 @@ do h.close() if not f then error(err) end - local res = f() - expect, field = res.expect, res.field + expect = f().expect end if _VERSION == "Lua 5.1" then @@ -584,257 +583,28 @@ function os.reboot() end end --- Install the lua part of the HTTP api (if enabled) -if http then - local nativeHTTPRequest = http.request +local bAPIError = false - local methods = { - GET = true, POST = true, HEAD = true, - OPTIONS = true, PUT = true, DELETE = true, - PATCH = true, TRACE = true, - } +local function load_apis(dir) + if not fs.isDir(dir) then return end - local function checkKey(options, key, ty, opt) - local value = options[key] - local valueTy = type(value) - - if (value ~= nil or not opt) and valueTy ~= ty then - error(("bad field '%s' (expected %s, got %s"):format(key, ty, valueTy), 4) - end - end - - local function checkOptions(options, body) - checkKey(options, "url", "string") - if body == false then - checkKey(options, "body", "nil") - else - checkKey(options, "body", "string", not body) - end - checkKey(options, "headers", "table", true) - checkKey(options, "method", "string", true) - checkKey(options, "redirect", "boolean", true) - - if options.method and not methods[options.method] then - error("Unsupported HTTP method", 3) - end - end - - local function wrapRequest(_url, ...) - local ok, err = nativeHTTPRequest(...) - if ok then - while true do - local event, param1, param2, param3 = os.pullEvent() - if event == "http_success" and param1 == _url then - return param2 - elseif event == "http_failure" and param1 == _url then - return nil, param2, param3 + for _, file in ipairs(fs.list(dir)) do + if file:sub(1, 1) ~= "." then + local path = fs.combine(dir, file) + if not fs.isDir(path) then + if not os.loadAPI(path) then + bAPIError = true end end end - return nil, err end - - http.get = function(_url, _headers, _binary) - if type(_url) == "table" then - checkOptions(_url, false) - return wrapRequest(_url.url, _url) - end - - expect(1, _url, "string") - expect(2, _headers, "table", "nil") - expect(3, _binary, "boolean", "nil") - return wrapRequest(_url, _url, nil, _headers, _binary) - end - - http.post = function(_url, _post, _headers, _binary) - if type(_url) == "table" then - checkOptions(_url, true) - return wrapRequest(_url.url, _url) - end - - expect(1, _url, "string") - expect(2, _post, "string") - expect(3, _headers, "table", "nil") - expect(4, _binary, "boolean", "nil") - return wrapRequest(_url, _url, _post, _headers, _binary) - end - - http.request = function(_url, _post, _headers, _binary) - local url - if type(_url) == "table" then - checkOptions(_url) - url = _url.url - else - expect(1, _url, "string") - expect(2, _post, "string", "nil") - expect(3, _headers, "table", "nil") - expect(4, _binary, "boolean", "nil") - url = _url.url - end - - local ok, err = nativeHTTPRequest(_url, _post, _headers, _binary) - if not ok then - os.queueEvent("http_failure", url, err) - end - return ok, err - end - - local nativeCheckURL = http.checkURL - http.checkURLAsync = nativeCheckURL - http.checkURL = function(_url) - expect(1, _url, "string") - local ok, err = nativeCheckURL(_url) - if not ok then return ok, err end - - while true do - local _, url, ok, err = os.pullEvent("http_check") - if url == _url then return ok, err end - end - end - - local nativeWebsocket = http.websocket - http.websocketAsync = nativeWebsocket - http.websocket = function(_url, _headers) - expect(1, _url, "string") - expect(2, _headers, "table", "nil") - - local ok, err = nativeWebsocket(_url, _headers) - if not ok then return ok, err end - - while true do - local event, url, param = os.pullEvent( ) - if event == "websocket_success" and url == _url then - return param - elseif event == "websocket_failure" and url == _url then - return false, param - end - end - end -end - --- Install the lua part of the FS api -local tEmpty = {} -function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs) - expect(1, sPath, "string") - expect(2, sLocation, "string") - local bIncludeHidden = nil - if type(bIncludeFiles) == "table" then - bIncludeDirs = field(bIncludeFiles, "include_dirs", "boolean", "nil") - bIncludeHidden = field(bIncludeFiles, "include_hidden", "boolean", "nil") - bIncludeFiles = field(bIncludeFiles, "include_files", "boolean", "nil") - else - expect(3, bIncludeFiles, "boolean", "nil") - expect(4, bIncludeDirs, "boolean", "nil") - end - - bIncludeHidden = bIncludeHidden ~= false - bIncludeFiles = bIncludeFiles ~= false - bIncludeDirs = bIncludeDirs ~= false - local sDir = sLocation - local nStart = 1 - local nSlash = string.find(sPath, "[/\\]", nStart) - if nSlash == 1 then - sDir = "" - nStart = 2 - end - local sName - while not sName do - local nSlash = string.find(sPath, "[/\\]", nStart) - if nSlash then - local sPart = string.sub(sPath, nStart, nSlash - 1) - sDir = fs.combine(sDir, sPart) - nStart = nSlash + 1 - else - sName = string.sub(sPath, nStart) - end - end - - if fs.isDir(sDir) then - local tResults = {} - if bIncludeDirs and sPath == "" then - table.insert(tResults, ".") - end - if sDir ~= "" then - if sPath == "" then - table.insert(tResults, bIncludeDirs and ".." or "../") - elseif sPath == "." then - table.insert(tResults, bIncludeDirs and "." or "./") - end - end - local tFiles = fs.list(sDir) - for n = 1, #tFiles do - local sFile = tFiles[n] - if #sFile >= #sName and string.sub(sFile, 1, #sName) == sName and ( - bIncludeHidden or sFile:sub(1, 1) ~= "." or sName:sub(1, 1) == "." - ) then - local bIsDir = fs.isDir(fs.combine(sDir, sFile)) - local sResult = string.sub(sFile, #sName + 1) - if bIsDir then - table.insert(tResults, sResult .. "/") - if bIncludeDirs and #sResult > 0 then - table.insert(tResults, sResult) - end - else - if bIncludeFiles and #sResult > 0 then - table.insert(tResults, sResult) - end - end - end - end - return tResults - end - return tEmpty -end - -function fs.isDriveRoot(sPath) - expect(1, sPath, "string") - -- Force the root directory to be a mount. - return fs.getDir(sPath) == ".." or fs.getDrive(sPath) ~= fs.getDrive(fs.getDir(sPath)) end -- Load APIs -local bAPIError = false -local tApis = fs.list("rom/apis") -for _, sFile in ipairs(tApis) do - if string.sub(sFile, 1, 1) ~= "." then - local sPath = fs.combine("rom/apis", sFile) - if not fs.isDir(sPath) then - if not os.loadAPI(sPath) then - bAPIError = true - end - end - end -end - -if turtle and fs.isDir("rom/apis/turtle") then - -- Load turtle APIs - local tApis = fs.list("rom/apis/turtle") - for _, sFile in ipairs(tApis) do - if string.sub(sFile, 1, 1) ~= "." then - local sPath = fs.combine("rom/apis/turtle", sFile) - if not fs.isDir(sPath) then - if not os.loadAPI(sPath) then - bAPIError = true - end - end - end - end -end - -if pocket and fs.isDir("rom/apis/pocket") then - -- Load pocket APIs - local tApis = fs.list("rom/apis/pocket") - for _, sFile in ipairs(tApis) do - if string.sub(sFile, 1, 1) ~= "." then - local sPath = fs.combine("rom/apis/pocket", sFile) - if not fs.isDir(sPath) then - if not os.loadAPI(sPath) then - bAPIError = true - end - end - end - end -end +load_apis("rom/apis") +if http then load_apis("rom/apis/http") end +if turtle then load_apis("rom/apis/turtle") end +if pocket then load_apis("rom/apis/pocket") end if commands and fs.isDir("rom/apis/command") then -- Load command APIs diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/fs.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/fs.lua new file mode 100644 index 000000000..3b8326e6b --- /dev/null +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/fs.lua @@ -0,0 +1,147 @@ +--- @module fs + +local expect = dofile("rom/modules/main/cc/expect.lua") +local expect, field = expect.expect, expect.field + +local native = fs + +local fs = _ENV +for k, v in pairs(native) do fs[k] = v end + +--[[- Provides completion for a file or directory name, suitable for use with +@{_G.read}. + +When a directory is a possible candidate for completion, two entries are +included - one with a trailing slash (indicating that entries within this +directory exist) and one without it (meaning this entry is an immediate +completion candidate). `include_dirs` can be set to @{false} to only include +those with a trailing slash. + +@tparam[1] string path The path to complete. +@tparam[1] string location The location where paths are resolved from. +@tparam[1,opt=true] boolean include_files When @{false}, only directories will +be included in the returned list. +@tparam[1,opt=true] boolean include_dirs When @{false}, "raw" directories will +not be included in the returned list. + +@tparam[2] string path The path to complete. +@tparam[2] string location The location where paths are resolved from. +@tparam[2] { + include_dirs? = boolean, include_files? = boolean, + include_hidden? = boolean +} options +This table form is an expanded version of the previous syntax. The +`include_files` and `include_dirs` arguments from above are passed in as fields. + +This table also accepts the following options: + - `include_hidden`: Whether to include hidden files (those starting with `.`) + by default. They will still be shown when typing a `.`. + +@treturn { string... } A list of possible completion candidates. +@since 1.74 +@changed 1.101.0 +@usage Complete files in the root directory. + + read(nil, nil, function(str) + return fs.complete(str, "", true, false) + end) + +@usage Complete files in the root directory, hiding hidden files by default. + + read(nil, nil, function(str) + return fs.complete(str, "", { + include_files = true, + include_dirs = false, + included_hidden = false, + }) + end) +]] +function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs) + expect(1, sPath, "string") + expect(2, sLocation, "string") + local bIncludeHidden = nil + if type(bIncludeFiles) == "table" then + bIncludeDirs = field(bIncludeFiles, "include_dirs", "boolean", "nil") + bIncludeHidden = field(bIncludeFiles, "include_hidden", "boolean", "nil") + bIncludeFiles = field(bIncludeFiles, "include_files", "boolean", "nil") + else + expect(3, bIncludeFiles, "boolean", "nil") + expect(4, bIncludeDirs, "boolean", "nil") + end + + bIncludeHidden = bIncludeHidden ~= false + bIncludeFiles = bIncludeFiles ~= false + bIncludeDirs = bIncludeDirs ~= false + local sDir = sLocation + local nStart = 1 + local nSlash = string.find(sPath, "[/\\]", nStart) + if nSlash == 1 then + sDir = "" + nStart = 2 + end + local sName + while not sName do + local nSlash = string.find(sPath, "[/\\]", nStart) + if nSlash then + local sPart = string.sub(sPath, nStart, nSlash - 1) + sDir = fs.combine(sDir, sPart) + nStart = nSlash + 1 + else + sName = string.sub(sPath, nStart) + end + end + + if fs.isDir(sDir) then + local tResults = {} + if bIncludeDirs and sPath == "" then + table.insert(tResults, ".") + end + if sDir ~= "" then + if sPath == "" then + table.insert(tResults, bIncludeDirs and ".." or "../") + elseif sPath == "." then + table.insert(tResults, bIncludeDirs and "." or "./") + end + end + local tFiles = fs.list(sDir) + for n = 1, #tFiles do + local sFile = tFiles[n] + if #sFile >= #sName and string.sub(sFile, 1, #sName) == sName and ( + bIncludeHidden or sFile:sub(1, 1) ~= "." or sName:sub(1, 1) == "." + ) then + local bIsDir = fs.isDir(fs.combine(sDir, sFile)) + local sResult = string.sub(sFile, #sName + 1) + if bIsDir then + table.insert(tResults, sResult .. "/") + if bIncludeDirs and #sResult > 0 then + table.insert(tResults, sResult) + end + else + if bIncludeFiles and #sResult > 0 then + table.insert(tResults, sResult) + end + end + end + end + return tResults + end + + return {} +end + +--- Returns true if a path is mounted to the parent filesystem. +-- +-- The root filesystem "/" is considered a mount, along with disk folders and +-- the rom folder. Other programs (such as network shares) can exstend this to +-- make other mount types by correctly assigning their return value for getDrive. +-- +-- @tparam string path The path to check. +-- @treturn boolean If the path is mounted, rather than a normal file/folder. +-- @throws If the path does not exist. +-- @see getDrive +-- @since 1.87.0 +function fs.isDriveRoot(sPath) + expect(1, sPath, "string") + -- Force the root directory to be a mount. + return fs.getDir(sPath) == ".." or fs.getDrive(sPath) ~= fs.getDrive(fs.getDir(sPath)) +end diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua new file mode 100644 index 000000000..d0d5909ad --- /dev/null +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua @@ -0,0 +1,303 @@ +--[[- Make HTTP requests, sending and receiving data to a remote web server. + +@module http +@since 1.1 +@see local_ips To allow accessing servers running on your local network. +]] + +local expect = dofile("rom/modules/main/cc/expect.lua").expect + +local native = http +local nativeHTTPRequest = http.request + +local methods = { + GET = true, POST = true, HEAD = true, + OPTIONS = true, PUT = true, DELETE = true, + PATCH = true, TRACE = true, +} + +local function checkKey(options, key, ty, opt) + local value = options[key] + local valueTy = type(value) + + if (value ~= nil or not opt) and valueTy ~= ty then + error(("bad field '%s' (expected %s, got %s"):format(key, ty, valueTy), 4) + end +end + +local function checkOptions(options, body) + checkKey(options, "url", "string") + if body == false then + checkKey(options, "body", "nil") + else + checkKey(options, "body", "string", not body) + end + checkKey(options, "headers", "table", true) + checkKey(options, "method", "string", true) + checkKey(options, "redirect", "boolean", true) + + if options.method and not methods[options.method] then + error("Unsupported HTTP method", 3) + end +end + +local function wrapRequest(_url, ...) + local ok, err = nativeHTTPRequest(...) + if ok then + while true do + local event, param1, param2, param3 = os.pullEvent() + if event == "http_success" and param1 == _url then + return param2 + elseif event == "http_failure" and param1 == _url then + return nil, param2, param3 + end + end + end + return nil, err +end + +--[[- Make a HTTP GET request to the given url. + +@tparam string url The url to request +@tparam[opt] { [string] = string } headers Additional headers to send as part +of this request. +@tparam[opt] boolean binary Whether to make a binary HTTP request. If true, +the body will not be UTF-8 encoded, and the received response will not be +decoded. + +@tparam[2] { + url = string, headers? = { [string] = string }, + binary? = boolean, method? = string, redirect? = boolean, +} request Options for the request. See @{http.request} for details on how +these options behave. + +@treturn Response The resulting http response, which can be read from. +@treturn[2] nil When the http request failed, such as in the event of a 404 +error or connection timeout. +@treturn string A message detailing why the request failed. +@treturn Response|nil The failing http response, if available. + +@changed 1.63 Added argument for headers. +@changed 1.80pr1 Response handles are now returned on error if available. +@changed 1.80pr1 Added argument for binary handles. +@changed 1.80pr1.6 Added support for table argument. +@changed 1.86.0 Added PATCH and TRACE methods. + +@usage Make a request to [example.tweaked.cc](https://example.tweaked.cc), +and print the returned page. + +```lua +local request = http.get("https://example.tweaked.cc") +print(request.readAll()) +-- => HTTP is working! +request.close() +``` +]] +function get(_url, _headers, _binary) + if type(_url) == "table" then + checkOptions(_url, false) + return wrapRequest(_url.url, _url) + end + + expect(1, _url, "string") + expect(2, _headers, "table", "nil") + expect(3, _binary, "boolean", "nil") + return wrapRequest(_url, _url, nil, _headers, _binary) +end + +--[[- Make a HTTP POST request to the given url. + +@tparam string url The url to request +@tparam string body The body of the POST request. +@tparam[opt] { [string] = string } headers Additional headers to send as part +of this request. +@tparam[opt] boolean binary Whether to make a binary HTTP request. If true, +the body will not be UTF-8 encoded, and the received response will not be +decoded. + +@tparam[2] { + url = string, body? = string, headers? = { [string] = string }, + binary? = boolean, method? = string, redirect? = boolean, +} request Options for the request. See @{http.request} for details on how +these options behave. + +@treturn Response The resulting http response, which can be read from. +@treturn[2] nil When the http request failed, such as in the event of a 404 +error or connection timeout. +@treturn string A message detailing why the request failed. +@treturn Response|nil The failing http response, if available. + +@since 1.31 +@changed 1.63 Added argument for headers. +@changed 1.80pr1 Response handles are now returned on error if available. +@changed 1.80pr1 Added argument for binary handles. +@changed 1.80pr1.6 Added support for table argument. +@changed 1.86.0 Added PATCH and TRACE methods. +]] +function post(_url, _post, _headers, _binary) + if type(_url) == "table" then + checkOptions(_url, true) + return wrapRequest(_url.url, _url) + end + + expect(1, _url, "string") + expect(2, _post, "string") + expect(3, _headers, "table", "nil") + expect(4, _binary, "boolean", "nil") + return wrapRequest(_url, _url, _post, _headers, _binary) +end + +--[[- Asynchronously make a HTTP request to the given url. + +This returns immediately, a @{http_success} or @{http_failure} will be queued +once the request has completed. + +@tparam string url The url to request +@tparam[opt] string body An optional string containing the body of the +request. If specified, a `POST` request will be made instead. +@tparam[opt] { [string] = string } headers Additional headers to send as part +of this request. +@tparam[opt] boolean binary Whether to make a binary HTTP request. If true, +the body will not be UTF-8 encoded, and the received response will not be +decoded. + +@tparam[2] { + url = string, body? = string, headers? = { [string] = string }, + binary? = boolean, method? = string, redirect? = boolean, +} request Options for the request. + +This table form is an expanded version of the previous syntax. All arguments +from above are passed in as fields instead (for instance, +`http.request("https://example.com")` becomes `http.request { url = +"https://example.com" }`). + This table also accepts several additional options: + + - `method`: Which HTTP method to use, for instance `"PATCH"` or `"DELETE"`. + - `redirect`: Whether to follow HTTP redirects. Defaults to true. + +@see http.get For a synchronous way to make GET requests. +@see http.post For a synchronous way to make POST requests. + +@changed 1.63 Added argument for headers. +@changed 1.80pr1 Added argument for binary handles. +@changed 1.80pr1.6 Added support for table argument. +@changed 1.86.0 Added PATCH and TRACE methods. +]] +function request(_url, _post, _headers, _binary) + local url + if type(_url) == "table" then + checkOptions(_url) + url = _url.url + else + expect(1, _url, "string") + expect(2, _post, "string", "nil") + expect(3, _headers, "table", "nil") + expect(4, _binary, "boolean", "nil") + url = _url.url + end + + local ok, err = nativeHTTPRequest(_url, _post, _headers, _binary) + if not ok then + os.queueEvent("http_failure", url, err) + end + return ok, err +end + +local nativeCheckURL = native.checkURL + +--[[- Asynchronously determine whether a URL can be requested. + +If this returns `true`, one should also listen for @{http_check} which will +container further information about whether the URL is allowed or not. + +@tparam string url The URL to check. +@treturn true When this url is not invalid. This does not imply that it is +allowed - see the comment above. +@treturn[2] false When this url is invalid. +@treturn string A reason why this URL is not valid (for instance, if it is +malformed, or blocked). + +@see http.checkURL For a synchronous version. +]] +checkURLAsync = nativeCheckURL + +--[[- Determine whether a URL can be requested. + +If this returns `true`, one should also listen for @{http_check} which will +container further information about whether the URL is allowed or not. + +@tparam string url The URL to check. +@treturn true When this url is valid and can be requested via @{http.request}. +@treturn[2] false When this url is invalid. +@treturn string A reason why this URL is not valid (for instance, if it is +malformed, or blocked). + +@see http.checkURLAsync For an asynchronous version. + +@usage +```lua +print(http.checkURL("https://example.tweaked.cc/")) +-- => true +print(http.checkURL("http://localhost/")) +-- => false Domain not permitted +print(http.checkURL("not a url")) +-- => false URL malformed +``` +]] +function checkURL(_url) + expect(1, _url, "string") + local ok, err = nativeCheckURL(_url) + if not ok then return ok, err end + + while true do + local _, url, ok, err = os.pullEvent("http_check") + if url == _url then return ok, err end + end +end + +local nativeWebsocket = native.websocket + +--[[- Open a websocket. + +@tparam string url The websocket url to connect to. This should have the +`ws://` or `wss://` protocol. +@tparam[opt] { [string] = string } headers Additional headers to send as part +of the initial websocket connection. + +@treturn Websocket The websocket connection. +@treturn[2] false If the websocket connection failed. +@treturn string An error message describing why the connection failed. +@since 1.80pr1.1 +@changed 1.80pr1.3 No longer asynchronous. +@changed 1.95.3 Added User-Agent to default headers. +]] +websocketAsync = nativeWebsocket + +--[[- Asynchronously open a websocket. + +This returns immediately, a @{websocket_success} or @{websocket_failure} +will be queued once the request has completed. + +@tparam string url The websocket url to connect to. This should have the +`ws://` or `wss://` protocol. +@tparam[opt] { [string] = string } headers Additional headers to send as part +of the initial websocket connection. +@since 1.80pr1.3 +@changed 1.95.3 Added User-Agent to default headers. +]] +function websocket(_url, _headers) + expect(1, _url, "string") + expect(2, _headers, "table", "nil") + + local ok, err = nativeWebsocket(_url, _headers) + if not ok then return ok, err end + + while true do + local event, url, param = os.pullEvent( ) + if event == "websocket_success" and url == _url then + return param + elseif event == "websocket_failure" and url == _url then + return false, param + end + end +end diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua index 6d597f375..18fb1ed34 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/turtle/turtle.lua @@ -6,7 +6,7 @@ end --- The builtin turtle API, without any generated helper functions. -- --- @deprecated Historically this table behaved differently to the main turtle API, but this is no longer the base. You +-- @deprecated Historically this table behaved differently to the main turtle API, but this is no longer the case. You -- should not need to use it. native = turtle.native or turtle diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts index 93faec638..e681f7c58 100644 --- a/projects/forge/build.gradle.kts +++ b/projects/forge/build.gradle.kts @@ -247,6 +247,7 @@ val lintLua by tasks.registering(IlluaminateExec::class) { inputs.files(luaJavadoc) args = listOf("lint") + workingDir = rootProject.projectDir doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") } doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }