|
|
(4 intermediate revisions by the same user not shown) |
Line 25: |
Line 25: |
| end | | end |
|
| |
|
| M.json = (function()
| | local members = mw.loadData [[Module:Sandbox/data]] |
| --
| | members = mw.text.jsonDecode( |
| -- json.lua
| | (mw.ext.externaldata.getWebData({ |
| --
| | url = "https://tildeverse.org/members.json", |
| -- Copyright (c) 2020 rxi
| | data = "data=data", |
| --
| | regex = "/(?'data'[\\s\\S]*)/", |
| -- Permission is hereby granted, free of charge, to any person obtaining a copy of
| | format = 'text' |
| -- this software and associated documentation files (the "Software"), to deal in
| | }))[1]['data']).members |
| -- the Software without restriction, including without limitation the rights to
| |
| -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
| |
| -- of the Software, and to permit persons to whom the Software is furnished to do
| |
| -- so, subject to the following conditions:
| |
| --
| |
| -- The above copyright notice and this permission notice shall be included in all
| |
| -- copies or substantial portions of the Software.
| |
| --
| |
| -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
| |
| -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
| |
| -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
| |
| -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
| |
| -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
| |
| -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
| |
| -- SOFTWARE.
| |
| --
| |
|
| |
|
| local json = { _version = "0.1.2" } | | local function as_date(d) |
| | | local year, month, day = string.match(d, |
| ------------------------------------------------------------------------------- | | [[(%d+)-(%d+)-(%d+)T]]) |
| -- Encode
| | local t = os.time{ year = year, month = month, day = day} |
| -------------------------------------------------------------------------------
| | return os.date("%B %e, %Y", t) |
| | |
| local encode | |
| | |
| local escape_char_map = {
| |
| [ "\\" ] = "\\",
| |
| [ "\"" ] = "\"",
| |
| [ "\b" ] = "b",
| |
| [ "\f" ] = "f",
| |
| [ "\n" ] = "n",
| |
| [ "\r" ] = "r",
| |
| [ "\t" ] = "t",
| |
| }
| |
| | |
| local escape_char_map_inv = { [ "/" ] = "/" }
| |
| for k, v in pairs(escape_char_map) do
| |
| escape_char_map_inv[v] = k
| |
| end | | end |
|
| |
|
| | | local function admins(a) |
| local function escape_char(c) | | local t = {} |
| return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
| | for _, v in ipairs(a) do |
| | table.insert(t, '[' .. v[2] .. ' ' .. v[1] .. ']') |
| | end |
| | return table.concat(t, ', ') |
| end | | end |
|
| |
|
| | | M.list = function() |
| local function encode_nil(val)
| | local tbl = [=[ |
| return "null"
| | {|class="wikitable"| |
| end
| | ! Name |
| | | ! Description |
| | | ! Created |
| local function encode_table(val, stack)
| | ! Sysadmins |
| local res = {}
| | ! OS |
| stack = stack or {}
| | ! [[tilde.chat]] channel |
| | | ! Link]=] |
| -- Circular reference?
| | for _, v in ipairs(members) do |
| if stack[val] then error("circular reference") end
| | local s = "|-\n| " .. table.concat({ |
| | | v.name, |
| stack[val] = true
| | v.description, |
| | | as_date(v.created), |
| if rawget(val, 1) ~= nil or next(val) == nil then
| | admins(v.sysadmins), |
| -- Treat as array -- check keys are valid and it is not sparse
| | v.os, |
| local n = 0
| | v.channel, |
| for k in pairs(val) do
| | v.link |
| if type(k) ~= "number" then
| | }, "\n| ") |
| error("invalid table: mixed or invalid key types")
| | tbl = tbl .. "\n" .. s |
| end
| |
| n = n + 1
| |
| end
| |
| if n ~= #val then
| |
| error("invalid table: sparse array")
| |
| end
| |
| -- Encode
| |
| for i, v in ipairs(val) do | |
| table.insert(res, encode(v, stack))
| |
| end
| |
| stack[val] = nil
| |
| return "[" .. table.concat(res, ",") .. "]"
| |
| | |
| else
| |
| -- Treat as an object
| |
| for k, v in pairs(val) do
| |
| if type(k) ~= "string" then
| |
| error("invalid table: mixed or invalid key types")
| |
| end
| |
| table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
| |
| end
| |
| stack[val] = nil
| |
| return "{" .. table.concat(res, ",") .. "}"
| |
| end
| |
| end
| |
| | |
| | |
| local function encode_string(val)
| |
| return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
| |
| end
| |
| | |
| | |
| local function encode_number(val)
| |
| -- Check for NaN, -inf and inf
| |
| if val ~= val or val <= -math.huge or val >= math.huge then
| |
| error("unexpected number value '" .. tostring(val) .. "'")
| |
| end
| |
| return string.format("%.14g", val)
| |
| end
| |
| | |
| | |
| local type_func_map = {
| |
| [ "nil" ] = encode_nil,
| |
| [ "table" ] = encode_table,
| |
| [ "string" ] = encode_string,
| |
| [ "number" ] = encode_number,
| |
| [ "boolean" ] = tostring,
| |
| }
| |
| | |
| | |
| encode = function(val, stack)
| |
| local t = type(val)
| |
| local f = type_func_map[t]
| |
| if f then
| |
| return f(val, stack)
| |
| end
| |
| error("unexpected type '" .. t .. "'")
| |
| end
| |
| | |
| | |
| function json.encode(val)
| |
| return ( encode(val) )
| |
| end
| |
| | |
| | |
| -------------------------------------------------------------------------------
| |
| -- Decode
| |
| -------------------------------------------------------------------------------
| |
| | |
| local parse
| |
| | |
| local function create_set(...)
| |
| local res = {}
| |
| for i = 1, select("#", ...) do
| |
| res[ select(i, ...) ] = true
| |
| end
| |
| return res
| |
| end
| |
| | |
| local space_chars = create_set(" ", "\t", "\r", "\n")
| |
| local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
| |
| local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
| |
| local literals = create_set("true", "false", "null")
| |
| | |
| local literal_map = {
| |
| [ "true" ] = true,
| |
| [ "false" ] = false,
| |
| [ "null" ] = nil,
| |
| }
| |
| | |
| | |
| local function next_char(str, idx, set, negate)
| |
| for i = idx, #str do
| |
| if set[str:sub(i, i)] ~= negate then
| |
| return i
| |
| end
| |
| end
| |
| return #str + 1
| |
| end
| |
| | |
| | |
| local function decode_error(str, idx, msg)
| |
| local line_count = 1
| |
| local col_count = 1
| |
| for i = 1, idx - 1 do
| |
| col_count = col_count + 1
| |
| if str:sub(i, i) == "\n" then
| |
| line_count = line_count + 1
| |
| col_count = 1
| |
| end | | end |
| end
| | tbl = tbl .. "\n|}" |
| error( string.format("%s at line %d col %d", msg, line_count, col_count) )
| | return tbl |
| end
| |
| | |
| | |
| local function codepoint_to_utf8(n)
| |
| -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
| |
| local f = math.floor
| |
| if n <= 0x7f then
| |
| return string.char(n) | |
| elseif n <= 0x7ff then
| |
| return string.char(f(n / 64) + 192, n % 64 + 128)
| |
| elseif n <= 0xffff then
| |
| return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
| |
| elseif n <= 0x10ffff then
| |
| return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
| |
| f(n % 4096 / 64) + 128, n % 64 + 128)
| |
| end
| |
| error( string.format("invalid unicode codepoint '%x'", n) )
| |
| end
| |
| | |
| | |
| local function parse_unicode_escape(s)
| |
| local n1 = tonumber( s:sub(1, 4), 16 )
| |
| local n2 = tonumber( s:sub(7, 10), 16 )
| |
| -- Surrogate pair?
| |
| if n2 then
| |
| return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
| |
| else
| |
| return codepoint_to_utf8(n1)
| |
| end
| |
| end
| |
| | |
| | |
| local function parse_string(str, i)
| |
| local res = ""
| |
| local j = i + 1
| |
| local k = j
| |
| | |
| while j <= #str do
| |
| local x = str:byte(j)
| |
| | |
| if x < 32 then
| |
| decode_error(str, j, "control character in string")
| |
| | |
| elseif x == 92 then -- `\`: Escape
| |
| res = res .. str:sub(k, j - 1)
| |
| j = j + 1
| |
| local c = str:sub(j, j)
| |
| if c == "u" then
| |
| local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
| |
| or str:match("^%x%x%x%x", j + 1)
| |
| or decode_error(str, j - 1, "invalid unicode escape in string")
| |
| res = res .. parse_unicode_escape(hex)
| |
| j = j + #hex
| |
| else
| |
| if not escape_chars[c] then
| |
| decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
| |
| end
| |
| res = res .. escape_char_map_inv[c]
| |
| end
| |
| k = j + 1
| |
| | |
| elseif x == 34 then -- `"`: End of string
| |
| res = res .. str:sub(k, j - 1)
| |
| return res, j + 1
| |
| end
| |
| | |
| j = j + 1
| |
| end
| |
| | |
| decode_error(str, i, "expected closing quote for string")
| |
| end
| |
| | |
| | |
| local function parse_number(str, i)
| |
| local x = next_char(str, i, delim_chars)
| |
| local s = str:sub(i, x - 1)
| |
| local n = tonumber(s)
| |
| if not n then
| |
| decode_error(str, i, "invalid number '" .. s .. "'")
| |
| end
| |
| return n, x
| |
| end
| |
| | |
| | |
| local function parse_literal(str, i)
| |
| local x = next_char(str, i, delim_chars)
| |
| local word = str:sub(i, x - 1)
| |
| if not literals[word] then
| |
| decode_error(str, i, "invalid literal '" .. word .. "'")
| |
| end
| |
| return literal_map[word], x
| |
| end
| |
| | |
| | |
| local function parse_array(str, i)
| |
| local res = {}
| |
| local n = 1
| |
| i = i + 1
| |
| while 1 do
| |
| local x
| |
| i = next_char(str, i, space_chars, true)
| |
| -- Empty / end of array?
| |
| if str:sub(i, i) == "]" then
| |
| i = i + 1
| |
| break
| |
| end
| |
| -- Read token
| |
| x, i = parse(str, i)
| |
| res[n] = x
| |
| n = n + 1
| |
| -- Next token
| |
| i = next_char(str, i, space_chars, true)
| |
| local chr = str:sub(i, i)
| |
| i = i + 1
| |
| if chr == "]" then break end
| |
| if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
| |
| end
| |
| return res, i
| |
| end
| |
| | |
| | |
| local function parse_object(str, i)
| |
| local res = {}
| |
| i = i + 1
| |
| while 1 do
| |
| local key, val
| |
| i = next_char(str, i, space_chars, true)
| |
| -- Empty / end of object?
| |
| if str:sub(i, i) == "}" then
| |
| i = i + 1
| |
| break
| |
| end
| |
| -- Read key
| |
| if str:sub(i, i) ~= '"' then
| |
| decode_error(str, i, "expected string for key")
| |
| end
| |
| key, i = parse(str, i)
| |
| -- Read ':' delimiter
| |
| i = next_char(str, i, space_chars, true)
| |
| if str:sub(i, i) ~= ":" then
| |
| decode_error(str, i, "expected ':' after key")
| |
| end
| |
| i = next_char(str, i + 1, space_chars, true)
| |
| -- Read value
| |
| val, i = parse(str, i)
| |
| -- Set
| |
| res[key] = val
| |
| -- Next token
| |
| i = next_char(str, i, space_chars, true)
| |
| local chr = str:sub(i, i)
| |
| i = i + 1
| |
| if chr == "}" then break end
| |
| if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
| |
| end
| |
| return res, i
| |
| end
| |
| | |
| | |
| local char_func_map = {
| |
| [ '"' ] = parse_string,
| |
| [ "0" ] = parse_number,
| |
| [ "1" ] = parse_number,
| |
| [ "2" ] = parse_number,
| |
| [ "3" ] = parse_number,
| |
| [ "4" ] = parse_number,
| |
| [ "5" ] = parse_number,
| |
| [ "6" ] = parse_number,
| |
| [ "7" ] = parse_number,
| |
| [ "8" ] = parse_number,
| |
| [ "9" ] = parse_number,
| |
| [ "-" ] = parse_number,
| |
| [ "t" ] = parse_literal,
| |
| [ "f" ] = parse_literal,
| |
| [ "n" ] = parse_literal,
| |
| [ "[" ] = parse_array,
| |
| [ "{" ] = parse_object,
| |
| } | |
| | |
| | |
| parse = function(str, idx)
| |
| local chr = str:sub(idx, idx)
| |
| local f = char_func_map[chr]
| |
| if f then
| |
| return f(str, idx)
| |
| end
| |
| decode_error(str, idx, "unexpected character '" .. chr .. "'")
| |
| end
| |
| | |
| | |
| function json.decode(str)
| |
| if type(str) ~= "string" then
| |
| error("expected argument of type string, got " .. type(str))
| |
| end
| |
| local res, idx = parse(str, next_char(str, 1, space_chars, true))
| |
| idx = next_char(str, idx, space_chars, true)
| |
| if idx <= #str then
| |
| decode_error(str, idx, "trailing garbage")
| |
| end
| |
| return res
| |
| end
| |
| | |
| | |
| return json
| |
| end)()
| |
| | |
| M.tildeverse = M.json.decode[==[
| |
| {
| |
| "members": [
| |
| {
| |
| "description": "aussies.space is a tilde located in australia focused around australian tilde users.",
| |
| "link": "https://aussies.space",
| |
| "gopher": "aussies.space",
| |
| "name": "aussies.space",
| |
| "created": "2019-02-19T19:44:58.0Z",
| |
| "channel": "#aussie",
| |
| "os": "ubuntu lts",
| |
| "sysadmins": [
| |
| [
| |
| "fosslinux",
| |
| "https://fosslinux.me"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "cosmic.voyage is a tilde community based around a collaborative science-fiction universe.",
| |
| "link": "https://cosmic.voyage",
| |
| "gopher": "cosmic.voyage",
| |
| "name": "cosmic.voyage",
| |
| "created": "2018-11-20T04:43:32Z",
| |
| "channel": "#cosmic",
| |
| "os": "ubuntu lts",
| |
| "sysadmins": [
| |
| [
| |
| "tomasino",
| |
| "https://tomasino.org"
| |
| ],
| |
| [
| |
| "fosslinux",
| |
| "https://fosslinux.me"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "Ctrl-C Club is a Linux server offering free SSH and web accounts to users who want to build webpages, chat online, learn Linux, play text console games, or fiddle around writing software.",
| |
| "link": "http://ctrl-c.club",
| |
| "gopher": "",
| |
| "name": "ctrl-c.club",
| |
| "created": "2014-12-06T06:13:44Z",
| |
| "channel": "#ctrl-c",
| |
| "os": "ubuntu lts",
| |
| "sysadmins": [
| |
| [
| |
| "calamitous",
| |
| "http://ctrl-c.club/~calamitous/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "envs.net is a minimalist, non-commercial shared linux system and will always be free to use. - located in germany",
| |
| "link": "https://envs.net",
| |
| "gopher": "envs.net",
| |
| "name": "envs",
| |
| "created": "2018-08-11T22:21:07Z",
| |
| "channel": "#envs",
| |
| "os": "debian",
| |
| "sysadmins": [
| |
| [
| |
| "creme",
| |
| "https://envs.net/~creme/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "RadioFreqs.space is a .space for RadioFreqs to share, learn and make!",
| |
| "link": "https://radiofreqs.space",
| |
| "gopher": "radiofreqs.space",
| |
| "name": "radiofreqs.space",
| |
| "created": "2019-04-11T04:34:08Z",
| |
| "channel": "#radiofreqs, #hamradio",
| |
| "os": "openbsd",
| |
| "sysadmins": [
| |
| [
| |
| "nonlinear",
| |
| "https://nonlinear.radiofreqs.space"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "An experimental software community with a 199X aesthetic.",
| |
| "link": "http://rw.rs",
| |
| "gopher": "",
| |
| "name": "rw.rs",
| |
| "created": "2019-04-13T07:53:39Z",
| |
| "channel": "#rw.rs",
| |
| "os": "debian",
| |
| "sysadmins": [
| |
| [
| |
| "adsr",
| |
| "http://rw.rs/~adsr/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "Un tilde pero en castellano -- a small spanish-speaking tilde",
| |
| "link": "https://texto-plano.xyz",
| |
| "gopher": "texto-plano.xyz",
| |
| "name": "texto-plano.xyz",
| |
| "created": "2020-10-30T15:27:11.0Z",
| |
| "channel": "#texto-plano",
| |
| "os": "openbsd",
| |
| "sysadmins": [
| |
| [
| |
| "ffuentes",
| |
| "https://texto-plano.xyz/~ffuentes/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "thunix offers Shell (SSH) accounts, Web Hosting, Email Accounts, and many other things to people free of charge.",
| |
| "link": "https://www.thunix.net",
| |
| "gopher": "thunix.net",
| |
| "name": "thunix",
| |
| "created": "2019-02-10T03:13:23Z",
| |
| "channel": "#thunix",
| |
| "os": "debian stable",
| |
| "sysadmins": [
| |
| [
| |
| "fosslinux",
| |
| "https://www.thunix.net/~fosslinux"
| |
| ],
| |
| [
| |
| "naglfar",
| |
| "https://www.thunix.net/~naglfar"
| |
| ],
| |
| [
| |
| "ubergeek",
| |
| "https://www.thunix.net/~ubergeek"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "tilde.club is not a social network it is one tiny totally standard unix computer that people respectfully use together in their shared quest to build awesome web pages",
| |
| "link": "https://tilde.club",
| |
| "gopher": "tilde.club",
| |
| "name": "tilde.club",
| |
| "created": "2014-09-30T02:42:24Z",
| |
| "channel": "#club",
| |
| "os": "fedora",
| |
| "sysadmins": [
| |
| [
| |
| "deepend",
| |
| "https://tilde.club/~deepend/"
| |
| ],
| |
| [
| |
| "ben",
| |
| "https://tilde.club/~ben/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "A public-access UNIX system running OpenBSD",
| |
| "link": "https://tilde.institute",
| |
| "gopher": "tilde.institute",
| |
| "name": "tilde.institute",
| |
| "created": "2018-11-22T02:53:08Z",
| |
| "channel": "#institute",
| |
| "os": "openbsd",
| |
| "sysadmins": [
| |
| [
| |
| "gbmor",
| |
| "https://gbmor.dev"
| |
| ],
| |
| [
| |
| "kneezle",
| |
| "https://kneezle.tilde.institute/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "tilde.pink is a gopher-only tilde running on NetBSD open to everyone",
| |
| "link": "https://tilde.pink",
| |
| "gopher": "tilde.pink",
| |
| "name": "tilde.pink",
| |
| "created": "2019-04-14T00:23:59Z",
| |
| "channel": "#pink",
| |
| "os": "netbsd",
| |
| "sysadmins": [
| |
| [
| |
| "tiwesdaeg",
| |
| "gopher://tilde.pink/1/~tiwesdaeg/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "non-commercial space for teaching, learning, and enjoying the social medium of unix",
| |
| "link": "https://tilde.team",
| |
| "gopher": "tilde.team",
| |
| "name": "tilde.team",
| |
| "created": "2017-05-30T20:25:48Z",
| |
| "channel": "#team",
| |
| "os": "ubuntu lts and freebsd",
| |
| "sysadmins": [
| |
| [
| |
| "ben",
| |
| "https://ben.tilde.team/"
| |
| ],
| |
| [
| |
| "cmccabe",
| |
| "https://cmccabe.tilde.team/"
| |
| ],
| |
| [
| |
| "khuxkm",
| |
| "https://khuxkm.tilde.team/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "intentional digital community for making art, socializing, and learning",
| |
| "link": "https://tilde.town",
| |
| "gopher": "tilde.town",
| |
| "name": "tilde.town",
| |
| "created": "2014-10-11T16:02:25Z",
| |
| "channel": "#town",
| |
| "os": "ubuntu lts",
| |
| "sysadmins": [
| |
| [
| |
| "vilmibm",
| |
| "https://tilde.town/~vilmibm/"
| |
| ],
| |
| [
| |
| "equa",
| |
| "https://tilde.town/~equa/"
| |
| ],
| |
| [
| |
| "archangelic",
| |
| "https://tilde.town/~archangelic/"
| |
| ],
| |
| [
| |
| "l0010o0001l",
| |
| "https://tilde.town/~l0010o0001l/"
| |
| ]
| |
| ]
| |
| },
| |
| {
| |
| "description": "Social Community inside your Terminal with chat, email, games and more",
| |
| "link": "https://yourtilde.com",
| |
| "gopher": "yourtilde.com",
| |
| "name": "YourTilde",
| |
| "created": "2017-12-28T05:00:25Z",
| |
| "channel": "#YourTilde",
| |
| "os": "Fedora",
| |
| "sysadmins": [
| |
| [
| |
| "deepend",
| |
| "https://yourtilde.com/~deepend/"
| |
| ]
| |
| ]
| |
| }
| |
| ]
| |
| }
| |
| ]==]
| |
| | |
| local tildes = {}
| |
| | |
| for _, v in ipairs(M.tildeverse.members) do
| |
| table.insert(tildes, v.name)
| |
| end
| |
| | |
| M.list = function()
| |
| return '* [[' .. table.concat(tildes, "]]\n* [[") .. "]]\n" | |
| end | | end |
|
| |
|
| return M | | return M |