summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/lualatex/luahttp
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2023-06-12 20:16:13 +0000
committerKarl Berry <karl@freefriends.org>2023-06-12 20:16:13 +0000
commit37a92634199a5d311afccd7d1dcdcb5160df452c (patch)
treefcf20fd813645e7fadb53eacf166cf4e572cb9df /Master/texmf-dist/tex/lualatex/luahttp
parent08572b84d6eb93111b79c28ca9b673ba284a173c (diff)
luahttp (12jun23)
git-svn-id: svn://tug.org/texlive/trunk@67348 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/lualatex/luahttp')
-rw-r--r--Master/texmf-dist/tex/lualatex/luahttp/luahttp-display.lua278
-rw-r--r--Master/texmf-dist/tex/lualatex/luahttp/luahttp-fetch.lua199
-rw-r--r--Master/texmf-dist/tex/lualatex/luahttp/luahttp.sty91
3 files changed, 568 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/lualatex/luahttp/luahttp-display.lua b/Master/texmf-dist/tex/lualatex/luahttp/luahttp-display.lua
new file mode 100644
index 00000000000..1a41cfb22ca
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luahttp/luahttp-display.lua
@@ -0,0 +1,278 @@
+--[[
+-- This module is part of the LuaHTTP package
+-- The purpose of this module is to correctly display the data reveived from the fetch module.
+]]
+
+local moduleName = display
+local M = {}
+
+---------- Dependencies ------------------------
+local fetch = require("luahttp-fetch")
+
+---------- Local variables ---------------------
+local tmp_image_counter = 0 -- Counter for image names
+
+---------- Helper functions --------------------
+
+--- Displays an image using LuaTeX img.write function.
+-- The image has to be saved first in order to be written to the PDF-Document using LuaTeX.
+-- @param data image data
+-- @param width optional width in cm
+-- @param height optional height in cm
+-- @see search_and_escape
+local function display_image(data, width, height)
+ local tmp_image_name = '/tmp/tmp_image' -- filename of image saved temporarly
+ tmp_image_name = tmp_image_name .. tmp_image_counter
+
+ local width = width or nil
+ local height = height or nil
+ local f = assert(io.open(tmp_image_name, 'wb'))
+ f:write(data)
+ f:close()
+
+ -- LuaTeX does not provide built-in image scaling functions
+ local image = img.new({filename = tmp_image_name, width = width, height = height})
+ if image then
+ img.write(image)
+ tmp_image_counter = tmp_image_counter + 1
+ end
+end
+
+--- Prompts the user to display an image.
+-- If an image-URL is detected the user is asked to display the image or the plain URL.
+-- @see is_image_url
+local function prompt_user()
+ while true do
+ print("Do you want to display the image? (y/n)")
+ local answer = io.read()
+
+ if answer == 'y' then
+ return true
+ elseif answer == 'n' then
+ return false
+ else
+ print("Invalid answer. Please enter 'y' or 'n'.")
+ end
+ end
+end
+
+--- Searches the given URL for image extensions.
+-- @param url some URL
+-- @return true if an image extension was found, false otherwise
+-- @see search_and_escape
+local function is_image_url(url)
+ local image_extensions = { "jpg", "jpeg", "png", "gif" }
+ for _, ext in ipairs(image_extensions) do
+ if string.match(url, "%." .. ext) then
+ return true
+ end
+ end
+ return false
+end
+
+--- Searches the given value for special characters that cause problems in LaTeX-Documents.
+-- @param value single value of a table
+-- @return if no special characters where found the value is retured unchanged,
+-- if special characters where found the escaped value is returned,
+-- if an image-URL is detected and the user chooses to display that image nil is returned
+-- @see is_image_url, prompt_user
+local function search_and_escape(value)
+ local value = tostring(value)
+ if string.find(value, "^http") then
+ if is_image_url(value) then
+ print("\nLooks like this URL leads to an image: " .. value)
+ if prompt_user() then
+ local body = fetch.image(value)
+ display_image(body, "5cm")
+ return nil
+ else
+ print("\nEscaping URL: " .. value)
+ value = [[\url{]] .. value .. [[}]]
+ end
+ else
+ print("\nEscaping URL: " .. value)
+ value = [[\url{]] .. value .. [[}]]
+ end
+ else
+ local latex_special_chars = '([%%$%{%}&%#_%^%~])'
+ value = value:gsub(latex_special_chars, "\\%1")
+ end
+ return value
+end
+
+--- Prints a table to stdout.
+-- @param t tagle to print
+-- @param indent optional string used for indents
+local function print_table(t, indent)
+ indent = indent or ""
+ for k, v in pairs(t) do
+ if type(v) == "table" then
+ print(indent .. k .. ":")
+ print_table(v, indent .. " ")
+ else
+ print(indent .. k .. ": " .. tostring(v))
+ end
+ end
+end
+
+--- Converts a table to text which can be written to the PDF-Document
+-- The values of the table are first searched for special characters.
+-- @param tbl table to be converted
+-- @return text
+-- @see search_and_escape
+local function table_to_text(tbl)
+ local results = {}
+ for k, v in pairs(tbl) do
+ if type(v) == "table" then
+ table.insert(results, table_to_text(v))
+ else
+ v = search_and_escape(v)
+ if v then
+ table.insert(results, v .. " \\\\ ")
+ end
+ end
+ end
+ return table.concat(results, " \\ ")
+end
+
+--- Check if a table contains a certain value.
+-- @param table input table
+-- @param target_value value to be searched
+-- @return true if value was found, false otherwise
+local function table_contains(table, target_value)
+ for _, value in pairs(table) do
+ if type(value) == "table" then
+ table_contains(value, target_value)
+ elseif value == target_value then
+ return true
+ end
+ end
+ return false
+end
+
+--- Filter out table entries that are not in the provided target keys.
+-- @param input_table
+-- @param target_keys array of target keys
+-- @param results used for recursion
+-- @return return a new table containing only the target keys and their values
+local function filter_table(input_table, target_keys, results)
+ local results = results or {}
+
+ for _, target_key in ipairs(target_keys) do
+ for key, value in pairs(input_table) do
+ if type(value) == "table" then
+ filter_table(value, target_keys, results)
+ elseif tostring(key) == target_key then
+ if not table_contains(results, value) then
+ table.insert(results, value)
+ break
+ end
+ end
+ end
+ end
+ return results
+end
+
+--- Converts a string containing a comma seperated list of elements to an array (ipairs).
+-- @param str input string
+-- @return table containing the elements as values
+local function string_to_ipairs(str)
+ local t = {}
+ for value in string.gmatch(str, "([^,]+)") do
+ table.insert(t, value)
+ end
+ return t
+end
+
+---------- Module functions --------------------
+
+--- Reads the contents of a JSON-file, filters the response and prints the result to the PDF-Document.
+-- @param json_file_path path to the JSON-file
+-- @param keys optional keys to filter out the relevant values from the response
+function M.json_using_file(json_file_path, keys)
+ local data = fetch.json_using_file(json_file_path)
+ print_table(data)
+ if keys then
+ local keys = string_to_ipairs(tostring(keys))
+ local values = filter_table(data, keys)
+ tex.sprint(table_to_text(values))
+ else
+ tex.sprint(table_to_text(data))
+ end
+end
+
+--- Prints the response filtered by the keys to the PDF-Document.
+-- @param url URL of the API
+-- @param keys optional keys to filter out the relevant values from the response
+function M.json(url, keys)
+ local data = fetch.json(tostring(url))
+ print_table(data)
+ if keys then
+ local keys = string_to_ipairs(tostring(keys))
+ local values = filter_table(data, keys)
+ tex.sprint(table_to_text(values))
+ else
+ tex.sprint(table_to_text(data))
+ end
+end
+
+--- Print an image to the PDF-Document.
+-- @param url URL of the image
+-- @param width optional width in cm
+-- @param height optional height in cm
+function M.image(url, width, height)
+ local data = fetch.image(tostring(url))
+ display_image(data, width, height)
+end
+
+--- Print values from an rss-feed to the PDF-Document.
+-- @param url URL of the feed
+-- @param limit limits the amount of entries that get printed to the PDF-Document
+-- @param feed_info_keys keys used to filter the feed information
+-- @param entry_keys keys used to filter the feed entries
+function M.rss(url, limit, feed_info_keys, entry_keys)
+ local data = fetch.rss(tostring(url))
+
+ if feed_info_keys then
+ local feed_info_keys = string_to_ipairs(tostring(feed_info_keys))
+ local feed = data.feed
+ local feed_info_filtered = filter_table(feed, feed_info_keys)
+
+ tex.sprint(table_to_text(feed_info_filtered))
+ end
+
+ local entries = {}
+
+ for i = 1, limit do
+ if data.entries[i] then
+ table.insert(entries, data.entries[i])
+ end
+ end
+
+ if entry_keys then
+ local entry_keys = string_to_ipairs(tostring(entry_keys))
+ local entries_filtered = filter_table(entries, entry_keys)
+
+ print_table(entries_filtered)
+ tex.sprint(table_to_text(entries_filtered))
+ else
+ tex.sprint(table_to_text(entries))
+ end
+end
+
+--- Print the reponse from a request using query parameters to the PDF-Document.
+-- @param url URL of the API
+-- @param keys keys to filter out the relevant values
+-- @param ... multiple optional query parameters used in the request
+function M.json_using_query(url, keys, ...)
+ local query_parameters = { ... }
+ local data = fetch.json_using_query(url, query_parameters)
+
+ print_table(data)
+
+ local keys = string_to_ipairs(tostring(keys))
+ local values = filter_table(data, keys)
+ tex.sprint(table_to_text(values))
+end
+
+return M
diff --git a/Master/texmf-dist/tex/lualatex/luahttp/luahttp-fetch.lua b/Master/texmf-dist/tex/lualatex/luahttp/luahttp-fetch.lua
new file mode 100644
index 00000000000..c39997173d3
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luahttp/luahttp-fetch.lua
@@ -0,0 +1,199 @@
+--[[
+-- This module is part of the LuaHTTP package
+-- The purpose of this module is to make HTTP requests and return the response.
+--
+-- Dependencies:
+-- dkjson
+-- luasec
+-- ltn12
+-- feedparser
+]]
+
+local moduleName = fetch
+local M = {}
+
+---------- Dependencies ------------------------
+local http = require("socket.http")
+local urlsocket = require("socket.url")
+local https = require("ssl.https")
+local dkjson = require("dkjson")
+local ltn12 = require("ltn12")
+local feedparser = require("feedparser")
+
+---------- Local variables ---------------------
+
+---------- Helper functions --------------------
+
+--- Makes an HTTP request using the provided request parameter.
+-- @param request table containing the request parameters
+-- @return the response as a table
+local function http_request(request)
+ local url = request.url
+ print("\nConnecting to " .. url)
+
+ -- Detect HTTPS
+ local client = http
+ if url:lower():find("^https://") then
+ client = https
+ end
+
+ -- Save optional body
+ local body = request.body
+
+ -- Prepare request
+ local response = {}
+ local request = {
+ method = request.method or "GET",
+ url = url,
+ headers = request.headers or nil,
+ redirect = request.redirect or false,
+ sink = ltn12.sink.table(response)
+ }
+
+ -- Send optional body
+ if body then
+ if type(body) == "table" then
+ body = dkjson.encode(body)
+ end
+ request.source = ltn12.source.string(body)
+ request.headers["Content-Length"] = #body
+ end
+
+ -- Make the request
+ local response_status, response_code, response_header, response_message = client.request(request)
+
+ local message = response_message or "(No response message recieved)"
+
+ if response_status == nil then
+ error("\n!!! Error connecting to " .. url .. "\nResponse: " .. response_code .. "\nMessage: " .. message)
+ end
+
+ -- Check for redirects and return body
+ if response_code == 301 or response_code == 302 or response_code == 303 then
+ print("\nResponse " .. message)
+ local redirect_url = response_header["location"]
+ if redirect_url == url then
+ error("\n!!! Error connecting to " .. url .. " results in a redirection loop")
+ else
+ print("\n!! Warning: redirecting to " .. redirect_url)
+ request.url = redirect_url
+ return http_request(request)
+ end
+ elseif response_code == 200 then
+ print("\nResponse " .. message)
+ if response == null or not next(response) then
+ error("\n!!! Error empty response")
+ end
+ return response
+ else
+ error("\n!!! Error connecting to " .. url .. "\nResponse: " .. response_code .. "\nMessage: " .. message)
+ end
+end
+
+--- Parse the given JSON-file.
+-- @param file_path path to JSON-file
+-- @return table containing the JSON data
+local function parse_json_file(file_path)
+ local file = io.open(file_path, "r")
+ local content = file:read("*all")
+ file:close()
+ return dkjson.decode(content)
+end
+
+--- Split a given string on the first occurence of a given character.
+-- @param str string containing the given character
+-- @param char target character at which the string gets split
+-- @return table containing the first part of the string as the key and the second part as the value
+local function split_first(str, char)
+ local result = {}
+ local pos = str:find(char)
+ local key = str:sub(1, pos - 1)
+ local value = str:sub(pos + 1)
+ result[key] = value
+ return result
+end
+
+---------- Module functions --------------------
+
+--- Make a GET request using the provided URL
+-- @param url target URL
+-- @return table containg the response
+function M.json(url)
+ local request = {
+ method = "GET",
+ url = url,
+ headers = {
+ ["Accept"] = "application/json"
+ },
+ }
+ local response = http_request(request)
+ return dkjson.decode(table.concat(response))
+end
+
+--- Make a request using the provided JSON-file
+-- @param json_file_path path to JSON-file
+-- @return table containg the response
+function M.json_using_file(json_file_path)
+ print("\nUsing file " .. json_file_path)
+ local request = parse_json_file(json_file_path)
+ local response = http_request(request)
+ return dkjson.decode(table.concat(response))
+end
+
+--- Make a GET request using the provided URL
+-- @param url target URL
+-- @return table containg the response
+function M.rss(url)
+ local request = {
+ method = "GET",
+ url = url,
+ headers = {
+ ["Accept"] = "application/rss+xml"
+ },
+ }
+ local response = http_request(request)
+ return feedparser.parse(table.concat(response))
+end
+
+--- Fetch image data using the provided URL
+-- @param url target URL leading to an image
+-- @return image data
+function M.image(url)
+ local request = {
+ method = "GET",
+ url = url
+ }
+ local response = http_request(request)
+ return table.concat(response)
+end
+
+--- Make a POST request using the provided URL and query parameters
+-- @param url target URL
+-- @param query_parameters parameters sent in the URL
+-- @return table containg the response
+function M.json_using_query(url, query_parameters)
+ local url = url
+ for _, value in ipairs(query_parameters) do
+ local params = split_first(value, "=")
+ for k, v in pairs(params) do
+ v = string.gsub(v, "\n", "")
+ url = url .. k .. "=" .. urlsocket.escape(v)
+ end
+ end
+
+ print("\nURL: " .. url)
+
+ local request = {
+ method = "POST",
+ url = url,
+ headers = {
+ ["Accept"] = "application/json",
+ ["Content-Type"] = "application/x-www-form-urlencoded";
+ },
+ redirect = false
+ }
+ local response = http_request(request)
+ return dkjson.decode(table.concat(response))
+end
+
+return M
diff --git a/Master/texmf-dist/tex/lualatex/luahttp/luahttp.sty b/Master/texmf-dist/tex/lualatex/luahttp/luahttp.sty
new file mode 100644
index 00000000000..627687f3cb3
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luahttp/luahttp.sty
@@ -0,0 +1,91 @@
+%% luahttp.sty
+%% Copyright 2023 Johannes Casaburi
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% https://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2008 or later.
+%
+% This work has the LPPL maintenance status `maintained'.
+%
+% The Current Maintainer of this work is Johannes Casaburi (johannes.casaburi@protonmail.com).
+%
+% This work consists of the files luahttp.sty, display.lua and fetch.lua.
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{luahttp}[LuaHTTP Package, Version 1.0.1]
+
+\RequirePackage{ifluatex}
+\RequirePackage{url}
+\RequirePackage{xparse}
+
+\ifluatex
+ \RequirePackage{luapackageloader}
+ \directlua{
+ version = 5.3
+ package.path = 'lua_modules/share/lua/' .. version .. '/?.lua;lua_modules/share/lua/' .. version .. '/?/init.lua;' .. package.path
+ package.cpath = 'lua_modules/lib/lua/' .. version .. '/?.so;' .. package.cpath
+ display = require("luahttp-display")
+ }
+
+ % \fetchJson{URL}[optional: "key1,key2,.."]
+ \NewDocumentCommand{\fetchJson}{m o}{
+ \IfNoValueTF{#2}
+ {\directlua{display.json("\luaescapestring{#1}")}} % without arg 2
+ {\directlua{display.json("\luaescapestring{#1}", "\luaescapestring{#2}")}}
+ }
+
+ % \fetchJsonUsingFile{JSON file}[optional: "key1,key2,.."]
+ \NewDocumentCommand{\fetchJsonUsingFile}{m o}{
+ \IfNoValueTF{#2}
+ {\directlua{display.json_using_file("\luaescapestring{#1}")}} % without arg 2
+ {\directlua{display.json_using_file("\luaescapestring{#1}", "\luaescapestring{#2}")}}
+ }
+
+ % \fetchJsonUsingQuery{URL}{"key1,key2,.."} [optional: "queryparameter1=value1"] .. [optional: "queryparameter5=value5"]
+ \NewDocumentCommand{\fetchJsonUsingQuery}{m m o o o o o}{
+ \IfNoValueTF{#7}
+ {\IfNoValueTF{#6}
+ {\IfNoValueTF{#5}
+ {\IfNoValueTF{#4}
+ {\IfNoValueTF{#3}
+ {\directlua{display.json_using_query("\luaescapestring{#1}", "\luaescapestring{#2}")}}
+ {\directlua{display.json_using_query("\luaescapestring{#1}", "\luaescapestring{#2}", "\luaescapestring{#3}")}}
+ }
+ {\directlua{display.json_using_query("\luaescapestring{#1}", "\luaescapestring{#2}", "\luaescapestring{#3}", "\luaescapestring{#4}")}}
+ }
+ {\directlua{display.json_using_query("\luaescapestring{#1}", "\luaescapestring{#2}", "\luaescapestring{#3}", "\luaescapestring{#4}", "\luaescapestring{#5}")}}
+ }
+ {\directlua{display.json_using_query("\luaescapestring{#1}", "\luaescapestring{#2}", "\luaescapestring{#3}", "\luaescapestring{#4}", "\luaescapestring{#5}", "\luaescapestring{#6}")}}
+ }
+ {\directlua{display.json_using_query("\luaescapestring{#1}", "\luaescapestring{#2}", "\luaescapestring{#3}", "\luaescapestring{#4}", "\luaescapestring{#5}", "\luaescapestring{#6}", "\luaescapestring{#7}")}}
+ }
+
+ % \fetchRss{URL}{limit}[optional: "feedinfokey1,feedinfokey2,.."][optional: "entrykey1,entrykey2,.."]
+ \NewDocumentCommand{\fetchRss}{m m o o}{
+ \IfNoValueTF{#4}
+ {\IfNoValueTF{#3}
+ {\directlua{display.rss("\luaescapestring{#1}", #2)}}
+ {\directlua{display.rss("\luaescapestring{#1}", #2, "\luaescapestring{#3}")}}
+ } % without arg 4
+ {\directlua{display.rss("\luaescapestring{#1}", #2, "\luaescapestring{#3}", "\luaescapestring{#4}")}
+ }
+ }
+
+ %\fetchImage{URL}[optional: width][optional: height]
+ \NewDocumentCommand{\fetchImage}{m o o}{
+ \IfNoValueTF{#2}
+ {\directlua{display.image("\luaescapestring{#1}")}} % whithout arg 2
+ {\IfNoValueTF{#3}
+ {\directlua{display.image("\luaescapestring{#1}", "\luaescapestring{#2}")}} % without arg 3
+ {\directlua{display.image("\luaescapestring{#1}", "\luaescapestring{#2}", "\luaescapestring{#3}")}}
+ }
+ }
+
+\else
+ \PackageError{luatexhttp}{LuaTeX is required}\@ehd
+ \expandafter\endinput % abort early
+\fi