added filesystem functions and requests module

This commit is contained in:
Kevin 2024-09-21 02:26:27 -03:00
parent 19ccfc0daf
commit 9032c6051f
4 changed files with 383 additions and 14 deletions

96
docs/module/requests.md Normal file
View File

@ -0,0 +1,96 @@
# Requests Module <!-- {docsify-ignore} -->
The `requests` module provides an interface for making asynchronous HTTP requests in AwesomeWM using the `lgi` bindings for `Soup`, `Gio`, and `GLib`.
It supports common HTTP methods like `GET`, `POST`, `PUT`, and more.
## Methods
### `requests.request(method, args, callback)`
#### Parameters:
- **method**: A string representing the HTTP method (`"GET"`, `"POST"`, etc.).
- **args**: A table or string. The table can include:
- **url**: The request URL (string).
- **params**: Query parameters (table).
- **headers**: HTTP headers (table).
- **body**: The request body (`GLib.Bytes` or string).
- **callback**: A function to handle the `Response` object.
#### Example:
```lua
requests.request("GET", { url = "https://api.example.com" }, function(response)
print(response.status_code, response.text)
end)
```
---
### `requests.get(args, callback)`
#### Description:
A shorthand for making `GET` requests.
#### Parameters:
- **args**: Similar to `requests.request` but defaults to `GET` method.
- **callback**: Function called with a `Response` object.
#### Example:
```lua
requests.get({ url = "https://api.example.com" }, function(response)
print(response.status_code, response.text)
end)
```
---
### `requests.post(args, callback)`
#### Description:
A shorthand for making `POST` requests.
#### Parameters:
- **args**: Similar to `requests.request` but defaults to `POST` method.
- **callback**: Function called with a `Response` object.
#### Example:
```lua
requests.post({
url = "https://api.example.com",
headers = { Authorization = "Bearer token" },
body = '{"key": "value"}'
}, function(response)
print(response.status_code, response.text)
end)
```
---
## Response Object
The `Response` object encapsulates the result of a request.
### Fields:
- **url**: The final URL after redirections.
- **status_code**: The HTTP status code (number).
- **ok**: A boolean indicating if the request was successful.
- **reason_phrase**: A string with the status reason.
- **text**: The response body as a string.
- **bytes**: The response body as `GLib.Bytes`.
### Example Usage:
```lua
print(response.url) -- "https://api.example.com"
print(response.status_code) -- 200
print(response.ok) -- true
print(response.text) -- Response body as string
```

View File

@ -1,6 +1,8 @@
local Gio = require("lgi").Gio local lgi = require("lgi")
local Gio = lgi.require("Gio", "2.0")
local GLib = lgi.require("GLib", "2.0")
local awful = require("awful") local awful = require("awful")
local string = string local gears = require("gears")
local _filesystem = {} local _filesystem = {}
@ -22,14 +24,14 @@ function _filesystem.list_directory_files(path, exts, recursive)
end end
-- Build a table of files from the path with the required extensions -- Build a table of files from the path with the required extensions
local file_list = Gio.File.new_for_path(path):enumerate_children( local file_list =
"standard::*", Gio.File.new_for_path(path):enumerate_children("standard::*", 0)
0
)
if file_list then if file_list then
for file in function() for file in
return file_list:next_file() function()
end do return file_list:next_file()
end
do
local file_type = file:get_file_type() local file_type = file:get_file_type()
if file_type == "REGULAR" then if file_type == "REGULAR" then
local file_name = file:get_display_name() local file_name = file:get_display_name()
@ -43,7 +45,7 @@ function _filesystem.list_directory_files(path, exts, recursive)
local file_name = file:get_display_name() local file_name = file:get_display_name()
files = gears.table.join( files = gears.table.join(
files, files,
list_directory_files(file_name, exts, recursive) _filesystem.list_directory_files(file_name, exts, recursive)
) )
end end
end end
@ -53,10 +55,106 @@ function _filesystem.list_directory_files(path, exts, recursive)
end end
function _filesystem.save_image_async_curl(url, filepath, callback) function _filesystem.save_image_async_curl(url, filepath, callback)
awful.spawn.with_line_callback(string.format("curl -L -s %s -o %s", url, filepath), awful.spawn.with_line_callback(
{ string.format("curl -L -s %s -o %s", url, filepath),
exit=callback {
}) exit = callback,
}
)
end
---@param filepath string | Gio.File
---@param callback fun(content: string)
---@return nil
function _filesystem.read_file_async(filepath, callback)
if type(filepath) == "string" then
return _filesystem.read_file_async(
Gio.File.new_for_path(filepath),
callback
)
elseif type(filepath) == "userdata" then
filepath:load_contents_async(nil, function(_, task)
local _, content, _ = filepath:load_contents_finish(task)
return callback(content)
end)
end
end
---@param filepath string | Gio.File
---@return string?
function _filesystem.read_file_sync(filepath)
if type(filepath) == "string" then
return _filesystem.read_file_sync(Gio.File.new_for_path(filepath))
elseif type(filepath) == "userdata" then
local _, content, _ = filepath:load_contents()
return content
end
end
---@param str string
local function tobytes(str)
local bytes = {}
for i = 1, #str do
table.insert(bytes, string.byte(str, i))
end
return bytes
end
---@param filepath string | Gio.File
---@param content string | string[] | GLib.Bytes
---@param callback? fun(file: Gio.File | userdata | nil): nil
function _filesystem.write_file_async(filepath, content, callback)
if type(filepath) == "string" then
return _filesystem.write_file_async(
Gio.File.new_for_path(filepath),
content,
callback
)
elseif type(content) == "string" then
return _filesystem.write_file_async(
filepath,
GLib.Bytes.new(tobytes(content)),
callback
)
elseif type(content) == "table" then
return _filesystem.write_file(
filepath,
table.concat(content, "\n"),
callback
)
elseif type(filepath) == "userdata" and type(content) == "userdata" then
callback = callback or function() end
return filepath:replace_contents_bytes_async(
content,
nil,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
nil,
function(_, task)
filepath:replace_contents_finish(task)
return callback(filepath)
end
)
end
end
function _filesystem.file_exists(filepath)
if filepath then
return GLib.file_test(filepath, GLib.FileTest.EXISTS)
else
return false
end
end
function _filesystem.dir_exists(filepath)
if filepath then
return GLib.file_test(filepath, GLib.FileTest.IS_DIR)
else
return false
end
end end
return _filesystem return _filesystem

View File

@ -4,4 +4,5 @@ return {
filesystem = require(... .. ".filesystem"), filesystem = require(... .. ".filesystem"),
shape = require(... .. ".shape"), shape = require(... .. ".shape"),
time = require(... .. ".time"), time = require(... .. ".time"),
requests = require(... .. ".requests"),
} }

174
helpers/requests.lua Normal file
View File

@ -0,0 +1,174 @@
local lgi = require("lgi")
local Soup = lgi.require("Soup", "3.0")
local Gio = lgi.require("Gio", "2.0")
local GLib = lgi.require("GLib", "2.0")
local bit = require("bit")
local gears = require("gears")
---@class Response
---@field url string
---@field status_code number
---@field ok boolean
---@field reason_phrase string
---@field stream userdata
---@field text string
---@field bytes GLib.Bytes
local Response = {}
-- ---@return table
-- Response.json = function(self)
-- local json = require("lib.json")
-- return json.decode(self.text)
-- end
function Response.new(url, status_code, ok, reason_phrase, input_stream)
local self = setmetatable({}, Response)
self.url = url
self.status_code = status_code
self.ok = ok
self.reason_phrase = reason_phrase
self.stream = input_stream
self.text = ""
self.bytes = nil
return self
end
---@param params table<string, any>
---@param parent_key string | nil
---@return string
local function encode_query_params(params, parent_key)
local encoded_params = {}
local encoded_val, encoded_key
for key, value in pairs(params) do
local full_key = parent_key and (parent_key .. "[" .. key .. "]") or key
encoded_key = GLib.Uri.escape_string(tostring(full_key), nil, true)
if type(value) == "table" then
table.insert(encoded_params, encode_query_params(value, full_key))
else
encoded_val = GLib.Uri.escape_string(tostring(value), nil, true)
table.insert(encoded_params, encoded_key .. "=" .. encoded_val)
end
end
return table.concat(encoded_params, "&")
end
---@class request
local requests = {}
---@alias request_args string | { url: string, params: table<string, any>, headers: table<string, string>, body: GLib.Bytes | string}
---@param method "GET" | "POST" | "PUT" | "DELETE" | "PATCH"
---@param args request_args
---@param callback fun(response: Response): nil
---@return nil
function requests.request(method, args, callback)
if type(args) == "string" then
args = gears.table.crush(
{ url = "", params = {} },
{ url = args },
false
)
else
args = gears.table.crush({ url = "", params = {} }, args, false)
end
local session, message, status_code, input_stream, ok, r, output_stream
session = Soup.Session.new()
if args.params then
args.url =
string.format("%s?%s", args.url, encode_query_params(args.params))
end
message = Soup.Message.new_from_uri(
method,
GLib.Uri.parse(args.url, GLib.UriFlags.NONE)
)
if args.headers then
for header_name, header_value in pairs(args.headers) do
message:get_request_headers():append(header_name, header_value)
end
end
if type(args.body) == "string" then
message.set_request_body_from_bytes(
nil,
GLib.Bytes.new({
string.byte(args.body, 1, #args.body),
})
)
end
return session:send_async(
message,
GLib.PRIORITY_DEFAULT,
nil,
function(_, task)
input_stream = session:send_finish(task)
status_code = message.status_code
ok = status_code >= 200 and status_code < 300
r = Response.new(
message.uri:to_string(),
status_code,
ok,
message.reason_phrase,
input_stream
)
output_stream = Gio.MemoryOutputStream.new_resizable()
return output_stream:splice_async(
r.stream,
bit.bor(
Gio.OutputStreamSpliceFlags.CLOSE_SOURCE,
Gio.OutputStreamSpliceFlags.CLOSE_TARGET
),
GLib.PRIORITY_DEFAULT,
nil,
function(_, t)
output_stream:splice_finish(t)
r.bytes = output_stream:steal_as_bytes()
r.text = r.bytes:get_data()
output_stream:flush_async(GLib.PRIORITY_DEFAULT, nil)
return callback(r)
end
)
end
)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.get(args, callback)
return requests.request("GET", args, callback)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.post(args, callback)
return requests.request("POST", args, callback)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.put(args, callback)
return requests.request("PUT", args, callback)
end
---@param args request_args
---@param callback fun(response:Response): nil
---@return nil
function requests.delete(args, callback)
return requests.request("DELETE", args, callback)
end
return requests