Initial commit
This commit is contained in:
424
Library/Malys/Utilities.md
Normal file
424
Library/Malys/Utilities.md
Normal file
@@ -0,0 +1,424 @@
|
||||
---
|
||||
author: malys
|
||||
description: List of reusable functions.
|
||||
name: "Library/Malys/Utilities"
|
||||
tags: meta/library
|
||||
share.uri: "https://github.com/malys/silverbullet-libraries/blob/main/src/Utilities.md"
|
||||
share.hash: 004bb007
|
||||
share.mode: pull
|
||||
---
|
||||
# Utilities
|
||||
|
||||
* `mls.getmeetingTitle()`: Extracts meeting title from page URL.
|
||||
* `mls.embedUrl(url, w, h)`: Embeds a URL using an iframe (width & height optional).
|
||||
* `mls.debug(message, prefix)`: Prints debug message (if `LOG_ENABLE` is true).
|
||||
* `mls.getCodeBlock(page, blockId, token, text)`: Retrieves code from a Markdown code block by ID, with optional token replacement.
|
||||
* `mls.parseISODate(isoDate)`: Parses an ISO date string to a timestamp (potential error with `xoffset`).
|
||||
* `mls.getStdlibInternal()`: Gets a list of internal API keys (cached).
|
||||
* `mls.positionToLineColumn(text, pos)`: Converts a text position to line/column numbers.
|
||||
* `table.appendArray(a, b)`: Appends array `b` to array `a`.
|
||||
* `table.unique(array)`: Returns unique elements from an array.
|
||||
* `table.uniqueKVBy(array, keyFn)`: Returns unique key-value pairs based on a key function.
|
||||
* `table.mergeKV(t1, t2)`: Merges two tables (recursive for nested tables).
|
||||
* `table.map(t, fn)`: Applies a function to each element of an array.
|
||||
* `table.toMd(data)`: Converts a table (or JSON) to a Markdown table.
|
||||
|
||||
## Debug
|
||||
|
||||
### How to enable debug mode
|
||||
|
||||
* Create space-lua with:
|
||||
```lua
|
||||
LOG_ENABLE=true
|
||||
```
|
||||
|
||||
* Reload system.
|
||||
* Open Chrome Console
|
||||
* Add filter “[Client] [DEBUG]“
|
||||
## Code
|
||||
```space-lua
|
||||
-- priority: 20
|
||||
mls=mls or {}
|
||||
|
||||
-- Convert meeting note title
|
||||
-- Extracts the meeting title from the current page URL.
|
||||
-- Assumes the URL format is like "namespace/Meeting_Note Title".
|
||||
-- Splits the URL by "/", takes the last part, and then splits that by "_"
|
||||
-- to remove the "Meeting" prefix and joins the remaining parts with a space.
|
||||
function mls.getmeetingTitle()
|
||||
local t=string.split(string.split(editor.getCurrentPage(),"/")[#string.split(editor.getCurrentPage(),"/")],"_")
|
||||
table.remove(t,1)
|
||||
t=table.concat(t, " ")
|
||||
return t
|
||||
end
|
||||
|
||||
-- Embed external resources
|
||||
-- Creates an HTML iframe to embed an external URL within the current page.
|
||||
-- Allows specifying width and height, defaulting to "100%" and "400px" respectively.
|
||||
function mls.embedUrl(specOrUrl,w,h)
|
||||
local width = w or "100%"
|
||||
local height = h or "400px"
|
||||
return widget.html(dom.iframe {
|
||||
src=specOrUrl,
|
||||
style="width: " .. width .. "; height: " .. height
|
||||
})
|
||||
end
|
||||
|
||||
---------------------------------------------
|
||||
---- Debug ---
|
||||
---------------------------------------------
|
||||
-- Pretty-print any Lua value (tables included)
|
||||
-- Recursively prints a Lua value, handling tables with indentation.
|
||||
-- Prevents infinite recursion by limiting the depth to 5.
|
||||
local function dump(value, depth)
|
||||
--print("[DEBUG][TYPE]"..type(value)) -- commented out debug line
|
||||
depth = depth or 0
|
||||
if type(value) ~= "table" or (type(value) == "string" and value ~= "[object Object]") then
|
||||
return value
|
||||
end
|
||||
|
||||
-- Prevent going too deep (avoid infinite recursion)
|
||||
if depth > 5 then
|
||||
return "{ ... }"
|
||||
end
|
||||
|
||||
local indent = string.rep(" ", depth)
|
||||
local next_indent = string.rep(" ", depth + 1)
|
||||
|
||||
local parts = {}
|
||||
table.insert(parts, "{")
|
||||
|
||||
for k, v in pairs(value) do
|
||||
local key = tostring(k)
|
||||
local val
|
||||
if type(v) == "table" then
|
||||
val = dump(v, depth + 1)
|
||||
else
|
||||
val = v
|
||||
end
|
||||
|
||||
table.insert(parts, next_indent .. tostring(key) .. " = " .. tostring(val) .. ",")
|
||||
end
|
||||
|
||||
table.insert(parts, indent .. "}")
|
||||
|
||||
return table.concat(parts, "\n")
|
||||
end
|
||||
|
||||
|
||||
-- Debug function
|
||||
-- Prints a debug message to the console if LOG_ENABLE is true.
|
||||
-- Uses the dump function to pretty-print the message.
|
||||
-- Allows specifying a prefix for the debug message.
|
||||
function mls.debug(message, prefix)
|
||||
if not LOG_ENABLE then
|
||||
return message
|
||||
end
|
||||
local log_message = dump(message)
|
||||
|
||||
local result = "[DEBUG]"
|
||||
if prefix then
|
||||
result = result .. "[" .. tostring(prefix) .. "]"
|
||||
end
|
||||
|
||||
result = result .. " " .. tostring(log_message)
|
||||
print(result)
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
---------------------------------------------
|
||||
---- Code Block code extraction ---
|
||||
---------------------------------------------
|
||||
-- Get child of node
|
||||
-- Helper function to find a child node of a given type within a parent node.
|
||||
local getChild = function(node, type)
|
||||
for _, child in ipairs(node.children) do
|
||||
if child.type == type then
|
||||
return child
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get text of Node
|
||||
-- Helper function to recursively extract the text content from a node.
|
||||
local getText = function(node)
|
||||
if not node then
|
||||
return nil
|
||||
end
|
||||
if node.text then
|
||||
return node.text
|
||||
else
|
||||
for _, child in ipairs(node.children) do
|
||||
local text = getText(child)
|
||||
if text then
|
||||
return text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Find codeblock
|
||||
-- Recursively searches for a code block (FencedCode node) with a specific blockId in the node's children.
|
||||
local findMyFence = function(node,blockId)
|
||||
if not node.children then
|
||||
return nil
|
||||
end
|
||||
for _, child in ipairs(node.children) do
|
||||
--mls.debug(child) -- commented out debug line
|
||||
if child.type == "FencedCode" then
|
||||
local info = getText(getChild(child, "CodeInfo"))
|
||||
--mls.debug(info) -- commented out debug line
|
||||
if info and info:find(blockId) then
|
||||
mls.debug(info)
|
||||
return getChild(child, "CodeText")
|
||||
end
|
||||
end
|
||||
local result= findMyFence(child,blockId)
|
||||
if result ~=nil then
|
||||
return result
|
||||
end
|
||||
--break -- for loop
|
||||
end --for
|
||||
end
|
||||
|
||||
-- Get code source in md codeblock
|
||||
-- Parses a Markdown page, finds a code block with the given blockId, and returns its content.
|
||||
-- Allows replacing a token within the code block with a new text value.
|
||||
function mls.getCodeBlock (page,blockId,token,text)
|
||||
local tree = markdown.parseMarkdown(space.readPage(page))
|
||||
--mls.debug(tree) -- commented out debug line
|
||||
if tree then
|
||||
local fence = findMyFence(tree,blockId)
|
||||
--mls.debug(fence) -- commented out debug line
|
||||
if fence then
|
||||
local result=fence.children[1].text
|
||||
if token == nil or text==nil then
|
||||
return result
|
||||
else
|
||||
return string.gsub(result, token, text)
|
||||
end
|
||||
end
|
||||
end
|
||||
return "Error"
|
||||
end
|
||||
|
||||
-- Parse ISO Date
|
||||
-- Parses an ISO date string into a Unix timestamp.
|
||||
-- This function appears incomplete and has a potential error in the offset calculation.
|
||||
-- The `xoffset` variable is not defined.
|
||||
function mls.parseISODate(isoDate)
|
||||
local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:%d+:%d+([Z%+%-])(%d?%d?)%:?(%d?%d?)"
|
||||
local year, month, day, hour, minute,
|
||||
seconds, offsetsign, offsethour, offsetmin = json_date:match(pattern)
|
||||
local timestamp = os.time{year = year, month = month,
|
||||
day = day, hour = hour, min = minute, sec = seconds}
|
||||
local offset = 0
|
||||
if offsetsign ~= 'Z' then
|
||||
offset = tonumber(offsethour) * 60 + tonumber(offsetmin)
|
||||
if xoffset == "-" then offset = offset * -1 end
|
||||
end
|
||||
|
||||
return timestamp + offset
|
||||
end
|
||||
|
||||
-- Get Stdlib Internal
|
||||
-- Retrieves a list of internal API keys from a remote file.
|
||||
-- Caches the results to avoid repeated fetching.
|
||||
-- The URL points to a file in the silverbulletmd/silverbullet repository.
|
||||
function mls.getStdlibInternal()
|
||||
if _G ~=nil then
|
||||
return table.keys(_G)
|
||||
end
|
||||
-- get list of internal api TODO: remove
|
||||
local KEY="stdlib_internal"
|
||||
local result=mls.cache.ttl.CacheManager.get(KEY)
|
||||
if result == nil then
|
||||
local url = "https://raw.githubusercontent.com/silverbulletmd/silverbullet/refs/heads/main/client/space_lua/stdlib.ts"
|
||||
local resp = net.proxyFetch(url)
|
||||
if resp.status ~= 200 then
|
||||
error("Failed to fetch file: " .. resp.status)
|
||||
end
|
||||
local content = resp.body
|
||||
local results = {}
|
||||
for line in string.split(content,"\n") do
|
||||
local key = string.match(string.trim(line), 'env%.set%("(.*)"')
|
||||
if key then
|
||||
table.insert(results, key)
|
||||
end
|
||||
end
|
||||
result=table.sort(results)
|
||||
mls.cache.ttl.CacheManager.set(KEY,result)
|
||||
end
|
||||
return table.sort(result)
|
||||
end
|
||||
|
||||
-- Position to Line Column
|
||||
-- Converts a character position within a text string to a line and column number.
|
||||
function mls.positionToLineColumn(text, pos)
|
||||
local line = 1
|
||||
local column = 0
|
||||
local lastNewline = -1
|
||||
|
||||
for i = 0, pos - 1 do
|
||||
if string.sub(text, i + 1, i + 1) == "\n" then
|
||||
line = line + 1
|
||||
lastNewline = i
|
||||
column = 0
|
||||
else
|
||||
column = column + 1
|
||||
end
|
||||
end
|
||||
|
||||
return { line = line, column = column }
|
||||
end
|
||||
|
||||
-----------------------------------------
|
||||
-- TABLE
|
||||
-----------------------------------------
|
||||
-- Append Array
|
||||
-- Appends all elements from one array to another.
|
||||
function table.appendArray(a, b)
|
||||
if a~= nil and b ~= nil then
|
||||
for i = 1, #b do
|
||||
table.insert(a, b[i])
|
||||
end
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
-- Unique
|
||||
-- Returns a new array containing only the unique elements from the input array.
|
||||
function table.unique(array)
|
||||
local seen = {}
|
||||
local result = {}
|
||||
|
||||
for i = 1, #array do
|
||||
local v = array[i]
|
||||
if not seen[v] then
|
||||
seen[v] = true
|
||||
result[#result + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Unique KV By
|
||||
-- Returns a new array containing only the unique key-value pairs from the input array,
|
||||
-- based on a provided key function.
|
||||
function table.uniqueKVBy(array, keyFn)
|
||||
local seen = {}
|
||||
local result = {}
|
||||
|
||||
for i = 1, #array do
|
||||
local key = keyFn(array[i])
|
||||
if not seen[key] then
|
||||
seen[key] = true
|
||||
result[#result + 1] = array[i]
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- Merge KV
|
||||
-- Merges two tables, recursively merging nested tables.
|
||||
-- If a key exists in both tables and the values are both tables, the nested tables are merged.
|
||||
-- Otherwise, the value from the second table overwrites the value from the first table.
|
||||
function table.mergeKV(t1, t2)
|
||||
for k, v in pairs(t2) do
|
||||
if type(v) == "table" and type(t1[k]) == "table" then
|
||||
mergeTablesRecursive(t1[k], v)
|
||||
else
|
||||
t1[k] = v
|
||||
end
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
-- Map
|
||||
-- Applies a function to each element of an array and returns a new array with the results.
|
||||
function table.map(t, fn)
|
||||
local result = {}
|
||||
for i, v in ipairs(t) do
|
||||
result[i] = fn(v, i)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- To Md
|
||||
-- Converts a Lua table to a Markdown table string.
|
||||
-- Handles both arrays of arrays and arrays of objects.
|
||||
-- Can parse JSON or Lua strings as input.
|
||||
function table.toMd(data)
|
||||
local tbl
|
||||
local input=string.trim(data)
|
||||
-- If input is a string, try parsing as JSON
|
||||
if string.startsWith(data,"{") then
|
||||
-- Lua
|
||||
tbl=spacelua.evalExpression(spacelua.parseExpression(data))
|
||||
else
|
||||
--JSON
|
||||
tbl=js.tolua(js.window.JSON.parse(js.tojs(data)))
|
||||
end
|
||||
if #tbl == 0 then return "" end
|
||||
|
||||
local md = {}
|
||||
|
||||
-- Helper to convert a row to Markdown
|
||||
local function rowToMd(row)
|
||||
local cells = {}
|
||||
for _, cell in ipairs(row) do
|
||||
table.insert(cells, tostring(cell))
|
||||
end
|
||||
return "| " .. table.concat(cells, " | ") .. " |"
|
||||
end
|
||||
|
||||
-- Handle array of objects
|
||||
local first = tbl[1]
|
||||
local headers
|
||||
if type(first) == "table" and not (#first > 0) then
|
||||
headers = {}
|
||||
for k in pairs(first) do table.insert(headers, k) end
|
||||
local rows = {headers}
|
||||
for _, obj in ipairs(tbl) do
|
||||
local row = {}
|
||||
for _, h in ipairs(headers) do
|
||||
table.insert(row, obj[h] or "")
|
||||
end
|
||||
table.insert(rows, row)
|
||||
end
|
||||
tbl = rows
|
||||
end
|
||||
|
||||
-- Header
|
||||
table.insert(md, rowToMd(tbl[1]))
|
||||
|
||||
-- Separator
|
||||
local sep = {}
|
||||
for _ = 1, #tbl[1] do table.insert(sep, "---") end
|
||||
table.insert(md, "| " .. table.concat(sep, " | ") .. " |")
|
||||
|
||||
-- Data rows
|
||||
for i = 2, #tbl do
|
||||
table.insert(md, rowToMd(tbl[i]))
|
||||
end
|
||||
|
||||
return table.concat(md, "\n")
|
||||
end
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2026-01-22:
|
||||
* getStdlibInternal compatible with edge version https://community.silverbullet.md/t/risk-audit/3562/14
|
||||
* 2026-01-20:
|
||||
* feat: add table functions
|
||||
* map
|
||||
* mergeKV
|
||||
* uniqueKVBy
|
||||
* unique
|
||||
* appendArray
|
||||
* feat: add `mls.getStdlibInternal` fucntion
|
||||
|
||||
Reference in New Issue
Block a user