Loosely based on Franciszek
78 | Wawrzak's CodeMirror
79 | 1 mode. One configuration parameter is
80 | supported, specials, to which you can provide an
81 | array of strings to have those identifiers highlighted with
82 | the lua-special style.
83 |
MIME types defined:text/x-lua.
84 |
85 |
86 |
--------------------------------------------------------------------------------
/servers/twitch_fragments/setup.lua:
--------------------------------------------------------------------------------
1 | twitch_display_lines = {}
2 | gui = gui or GuiCreate()
3 | xpos = xpos or 80
4 | ypos = ypos or 30
5 | randomOnNoVotes = false
6 | math.randomseed(os.time())
7 |
8 | function draw_twitch_display()
9 | GuiStartFrame( gui )
10 | GuiLayoutBeginVertical( gui, xpos, ypos )
11 | for idx, line in ipairs(twitch_display_lines) do
12 | GuiText(gui, 0, 0, line)
13 | end
14 | GuiLayoutEnd( gui )
15 | end
16 |
17 | function set_countdown(timeleft)
18 | twitch_display_lines = {"Next vote: " .. timeleft .. "s"}
19 | end
20 |
21 | outcomes = {}
22 | function clear_outcomes()
23 | outcomes = {}
24 | end
25 |
26 | function clear_display()
27 | twitch_display_lines = {}
28 | end
29 |
30 | function add_outcome(outcome)
31 | table.insert(outcomes, outcome)
32 | end
33 |
34 | outcome_generators = {}
35 |
36 | function draw_outcomes(n)
37 | local candidates = {}
38 | for idx, generator in ipairs(outcome_generators) do
39 | candidates[idx] = {math.random(), generator}
40 | end
41 | table.sort(candidates, function(a, b) return a[1] < b[1] end)
42 | clear_outcomes()
43 | for idx = 1, n do
44 | add_outcome(candidates[idx][2]())
45 | end
46 | end
47 |
48 | function update_outcome_display(vote_time_left)
49 | twitch_display_lines = {"Voting ends: " .. vote_time_left}
50 | for idx, outcome in ipairs(outcomes) do
51 | table.insert(
52 | twitch_display_lines,
53 | ("%d> %s (%d)"):format(idx, outcome.name, outcome.votes)
54 | )
55 | end
56 | end
57 |
58 | function set_votes(outcome_votes)
59 | for idx, outcome in ipairs(outcomes) do
60 | outcome.votes = outcome_votes[idx] or 0
61 | end
62 | end
63 |
64 | function do_winner()
65 | local best_outcome = outcomes[1]
66 | for idx, outcome in ipairs(outcomes) do
67 | if outcome.votes > best_outcome.votes then
68 | best_outcome = outcome
69 | end
70 | end
71 | if best_outcome.votes > 0 then
72 | GamePrintImportant(best_outcome.name, best_outcome.desc or "you voted for this")
73 | best_outcome:func()
74 | elseif randomOnNoVotes then
75 | local random_outcome = outcomes[math.random(1, 4)]
76 | GamePrintImportant("Nobody voted!", "Random option choosen: " .. random_outcome.name)
77 | random_outcome:func()
78 | else
79 | GamePrintImportant("Nobody voted!", "Remember to vote!")
80 | end
81 | clear_outcomes()
82 | clear_display()
83 | end
84 |
85 | add_persistent_func("twitch_gui", draw_twitch_display)
--------------------------------------------------------------------------------
/mod_ws_api/data/ws/pollws.lua:
--------------------------------------------------------------------------------
1 | if pollws then return end
2 |
3 | print("Attempting to link pollws.dll through FFI")
4 | local ffi = ffi or _G.ffi or require("ffi")
5 | ffi.cdef[[
6 | struct pollsocket* pollws_open(const char* url);
7 | void pollws_close(struct pollsocket* ctx);
8 | int pollws_status(struct pollsocket* ctx);
9 | void pollws_send(struct pollsocket* ctx, const char* msg);
10 | int pollws_poll(struct pollsocket* ctx);
11 | unsigned int pollws_get(struct pollsocket* ctx, char* dest, unsigned int dest_size);
12 | unsigned int pollws_pop(struct pollsocket* ctx, char* dest, unsigned int dest_size);
13 | ]]
14 | print("CDEF was OK")
15 | pollws = ffi.load("pollws")
16 | print("FFI was OK")
17 |
18 | local POLLWS_STATUS_CODES = {
19 | [-1] = "invalid",
20 | [ 0] = "closed",
21 | [ 1] = "opening",
22 | [ 2] = "open",
23 | [ 3] = "error"
24 | }
25 |
26 | function open_socket(url, scratch_size)
27 | -- might as well have a comfortable megabyte of space
28 | if not scratch_size then scratch_size = 1000000 end
29 | local res = {
30 | _socket = pollws.pollws_open(url),
31 | _scratch = ffi.new("int8_t[?]", scratch_size),
32 | _scratch_size = scratch_size
33 | }
34 | function res:set_scratch_size(scratch_size)
35 | self._scratch = ffi.new("int8_t[?]", scratch_size)
36 | self._scratch_size = scratch_size
37 | end
38 | function res:poll()
39 | if not self._socket then return end
40 | local msg_size = pollws.pollws_pop(self._socket, self._scratch, self._scratch_size)
41 | if msg_size > 0 then
42 | local smsg = ffi.string(self._scratch, msg_size)
43 | return smsg
44 | else
45 | return nil
46 | end
47 | end
48 | function res:send(msg)
49 | if not self._socket then return end
50 | pollws.pollws_send(self._socket, msg)
51 | end
52 | function res:close()
53 | pollws.pollws_close(self._socket)
54 | self._socket = nil
55 | end
56 | function res:raw_status()
57 | if not self._socket then return -1 end
58 | return pollws.pollws_status(self._socket)
59 | end
60 | function res:status()
61 | return POLLWS_STATUS_CODES[self:raw_status()] or "unknown"
62 | end
63 | function res:run_async(on_message)
64 | self.running = true
65 | async(function()
66 | while self._socket and self.running do
67 | local msg = self:poll()
68 | if msg then on_message(self, msg) end
69 | wait(1)
70 | end
71 | end)
72 | end
73 | function res:stop()
74 | self.running = false
75 | end
76 |
77 | return res
78 | end
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # noita-ws-api
4 | Websocket based API to Noita: allows Noita to connect through a websocket to a server, which can then execute
5 | arbitrary Lua within Noita's environment.
6 |
7 | ## Installation (mod / API host)
8 |
9 | The mod dynamically links the [pollws](https://github.com/probable-basilisk/pollws) library through
10 | luajit's FFI in order to act as a websocket client.
11 |
12 | * Grab the [pollws binaries](https://github.com/probable-basilisk/pollws/releases/download/0.1.0/pollws_0_1_0_windows.zip): copy
13 | the *32 bit* `pollws.dll` into your Noita install dir (so it should be in the same directory as `Noita.exe`). You don't need
14 | to care about the includes, bindings, or `.lib` files-- only the `.dll` is needed.
15 |
16 | * Copy the `mod_ws_api` directory into `{your_Noita_install_dir}/mods/`: you should end up with a file in
17 | `{your_Noita_install_dir}/mods/mod_ws_api/mod.xml`.
18 |
19 | * Enable the `mod_ws_api` mod in the Noita mods menu. It will ask for full permissions (in order to use the FFI); presumably
20 | you understand what that means and the risks involved.
21 |
22 | * (optional) Copy the `mod_dev_settings` directory into your mods dir. This mod disables Noita from pausing when
23 | it loses focus, which is very convenient when using the console. Enable this mod also in the Noita mods menu.
24 |
25 | ## Installation (example servers)
26 |
27 | The example servers run on nodejs, so get nodejs installed, and then
28 | ```
29 | cd {the_example_servers_dir}
30 | npm install
31 | ```
32 |
33 | ## Example: webconsole
34 | From the `servers` directory, run:
35 | ```
36 | node main_console.js
37 | ```
38 |
39 | A web browser window should automatically open to a Noita webconsole. Start up Noita (with `mod_ws` enabled),
40 | and then you can directly execute Lua commands from the webconsole. For example, try:
41 |
42 | ```Lua
43 | hello()
44 | GamePrintImportant("Big Message", "Sub Message")
45 | print_entity_info(get_player())
46 | help("EntityGetTransform")
47 | ```
48 |
49 | Note that there is rudimentary command completion with Tab, and that
50 | you can get help (if available) for Noita's API with `help("command")` or
51 | simply `command?`.
52 |
53 | ## Example: twitch interaction
54 | From the `servers` directory, run:
55 | ```
56 | node main_twitch.js your_twitch_channel_name
57 | ```
58 |
59 | Then start up Noita. You should see a voting thingy on the right side--
60 | users in the specified twitch channel can type the appropriate numbers
61 | to vote on things to happen in your game.
62 |
63 | You can also create a .env file in your `servers`, to setup a default twitch account name to use, and the time for voting. See `servers/.env.example` for reference
64 |
65 | ## API Overview
66 |
67 | The `mod_ws` mod acts as a websocket client, and automatically tries to connect to `ws://localhost:9090`.
68 |
69 | * Websocket messages sent *TO* Noita are treated as raw Lua source and directly executed as Lua
70 | * Noita will reply with two kinds of messages: JSON payloads, and raw strings. Raw strings are
71 | prefixed with `>`. Approximately every second Noita will send a heartbeat message, in JSON, of the
72 | form `{"kind": "heartbeat", "source": "noita"}`.
73 |
--------------------------------------------------------------------------------
/servers/main_console.js:
--------------------------------------------------------------------------------
1 | const ws = require('ws');
2 | const crypto = require('crypto');
3 | const open = require('open');
4 | const WS_PORT = 9090;
5 |
6 |
7 | const wss = new ws.Server({ port: WS_PORT });
8 | console.log("WS listening on " + WS_PORT);
9 |
10 | let noita = null;
11 | let con = null;
12 |
13 | function getConnectionName(ws) {
14 | return ws._socket.remoteAddress + ":" + ws._socket.remotePort;
15 | }
16 |
17 | function isConnectionLocalhost(ws) {
18 | const addr = ws._socket.remoteAddress
19 | return (addr == "::1") || (addr == "127.0.0.1") || (addr == "localhost") || (addr == "::ffff:127.0.0.1")
20 | }
21 |
22 | function randomToken() {
23 | return crypto.randomBytes(16).toString('hex');
24 | }
25 | const TOKEN = randomToken();
26 |
27 | wss.on('connection', function connection(ws) {
28 | const cname = getConnectionName(ws);
29 | console.log("New connection: " + cname);
30 | if (!isConnectionLocalhost(ws)) {
31 | console.log("Connection refused: not localhost!");
32 | ws.terminate();
33 | return
34 | }
35 |
36 | ws.on('message', function incoming(data, flags) {
37 | let jdata = null;
38 | // special case: if the string we get is prefixed with ">",
39 | // don't try to interpret as JSON, just treat it as a print
40 | if (data.slice(0, 1) == ">") {
41 | jdata = {kind: "print", text: data.slice(1)};
42 | } else {
43 | try {
44 | jdata = JSON.parse(data);
45 | } catch (e) {
46 | console.log(data);
47 | console.error(e);
48 | return;
49 | }
50 | }
51 |
52 | if (jdata["kind"] === "heartbeat") {
53 | if (jdata["source"] === "noita") {
54 | if (noita != ws) {
55 | console.log("Registering noita!");
56 | noita = ws;
57 | ws.send(`GamePrint('WS connected as ${cname}')`);
58 | ws.send("set_print_to_socket(true)");
59 | ws.send("print('Noita connected')");
60 | }
61 | } else if (jdata["source"] === "console") {
62 | if (con != ws) {
63 | if (jdata["token"] != TOKEN) {
64 | console.log("Invalid token! Got: " + jdata["token"]);
65 | ws.terminate();
66 | return;
67 | }
68 | console.log("Registering console!");
69 | con = ws;
70 | ws.send(JSON.stringify({kind: "print", text: `Connected as ${cname}`}));
71 | }
72 | } else {
73 | console.log("Unknown source: " + jdata["source"]);
74 | }
75 | } else if (ws === noita) {
76 | // send noita->con
77 | if (con != null) {
78 | con.send(JSON.stringify(jdata));
79 | }
80 | } else if (ws === con) {
81 | // con -> noita
82 | if (noita != null) {
83 | // Don't want to involve JSON parser on Noita end,
84 | // so send just the raw command
85 | noita.send(jdata["command"]);
86 | }
87 | }
88 | });
89 | ws.on('close', function closed(_ws, code, reason) {
90 | console.log("Connection closed: " + code + ", " + reason);
91 | if (ws === noita) {
92 | console.log("Noita closed");
93 | noita = null;
94 | }
95 | if (ws === con) {
96 | console.log("Console webpage closed");
97 | con = null;
98 | }
99 | });
100 | });
101 |
102 | const { join } = require('path');
103 | const polka = require('polka');
104 |
105 | const HTTP_PORT = 8080;
106 | const dir = join(__dirname, 'www_console');
107 | const serve = require('serve-static')(dir);
108 |
109 | const secret_dir = "/" + TOKEN
110 |
111 | polka()
112 | .use(secret_dir, serve)
113 | .listen(HTTP_PORT, err => {
114 | if (err) throw err;
115 | console.log(`Console on localhost:${HTTP_PORT}${secret_dir}`);
116 | open(`http://localhost:${HTTP_PORT}${secret_dir}`);
117 | });
118 |
--------------------------------------------------------------------------------
/mod_ws_api/data/ws/utils.lua:
--------------------------------------------------------------------------------
1 | function hello()
2 | GamePrintImportant("Hello", "Hello")
3 | GamePrint("Hello")
4 | print("Hello")
5 | end
6 |
7 | function get_player()
8 | return EntityGetWithTag( "player_unit" )[1]
9 | end
10 |
11 | function get_player_pos()
12 | return EntityGetTransform(get_player())
13 | end
14 |
15 | function get_closest_entity(px, py, tag)
16 | if not py then
17 | tag = px
18 | px, py = get_player_pos()
19 | end
20 | return EntityGetClosestWithTag( px, py, tag)
21 | end
22 |
23 | function get_entity_mouse(tag)
24 | local mx, my = DEBUG_GetMouseWorld()
25 | return get_closest_entity(mx, my, tag or "hittable")
26 | end
27 |
28 | function teleport(x, y)
29 | EntitySetTransform(get_player(), x, y)
30 | end
31 |
32 | function spawn_entity(ename, offset_x, offset_y)
33 | local x, y = get_player_pos()
34 | x = x + (offset_x or 0)
35 | y = y + (offset_y or 0)
36 | return EntityLoad(ename, x, y)
37 | end
38 |
39 | function print_component_info(c)
40 | local frags = {"<" .. ComponentGetTypeName(c) .. ">"}
41 | local members = ComponentGetMembers(c)
42 | if not members then return end
43 | for k, v in pairs(members) do
44 | table.insert(frags, k .. ': ' .. tostring(v))
45 | end
46 | print(table.concat(frags, '\n'))
47 | end
48 |
49 | function get_vector_value(comp, member, kind)
50 | kind = kind or "float"
51 | local n = ComponentGetVectorSize( comp, member, kind )
52 | if not n then return nil end
53 | local ret = {};
54 | for i = 1, n do
55 | ret[i] = ComponentGetVectorValue(comp, member, kind, i-1) or "nil"
56 | end
57 | return ret
58 | end
59 |
60 | function print_vector_value(...)
61 | local v = get_vector_value(...)
62 | if not v then return nil end
63 | return "{" .. table.concat(v, ", ") .. "}"
64 | end
65 |
66 | function print_detailed_component_info(c)
67 | local members = ComponentGetMembers(c)
68 | if not members then return end
69 | local frags = {}
70 | for k, v in pairs(members) do
71 | if (not v) or #v == 0 then
72 | local mems = ComponentObjectGetMembers(c, k)
73 | if mems then
74 | table.insert(frags, k .. ">")
75 | for k2, v2 in pairs(mems) do
76 | table.insert(frags, " " .. k2 .. ": " .. tostring(v2))
77 | end
78 | else
79 | v = print_vector_value(c, k)
80 | end
81 | end
82 | table.insert(frags, k .. ': ' .. tostring(v))
83 | end
84 | print(table.concat(frags, '\n'))
85 |
86 | end
87 |
88 | function print_entity_info(e)
89 | local comps = EntityGetAllComponents(e)
90 | if not comps then
91 | print("Invalid entity?")
92 | return
93 | end
94 | for idx, comp in ipairs(comps) do
95 | print(comp, "-----------------")
96 | print_component_info(comp)
97 | end
98 | end
99 |
100 | function list_components(e)
101 | local comps = EntityGetAllComponents(e)
102 | if not comps then
103 | print("Invalid entity?")
104 | return
105 | end
106 | for idx, comp in ipairs(comps) do
107 | print(comp .. " : " .. ComponentGetTypeName(comp))
108 | end
109 | end
110 |
111 | function list_funcs(filter)
112 | local ff = {}
113 | for k, v in pairs(getfenv()) do
114 | local first_letter = k:sub(1,1)
115 | if first_letter:upper() == first_letter then
116 | if (not filter) or k:lower():find(filter:lower()) then
117 | table.insert(ff, k)
118 | end
119 | end
120 | end
121 | table.sort(ff)
122 | print(table.concat(ff, "\n"))
123 | end
124 |
125 | function get_child_info(e)
126 | local children = EntityGetAllChildren(e)
127 | for _, child in ipairs(children) do
128 | print(child, EntityGetName(child) or "[no name]")
129 | end
130 | end
131 |
132 | function do_here(fn)
133 | local f = loadfile(fn)
134 | if type(f) ~= "function" then
135 | print("Loading error; check logger.txt for details.")
136 | end
137 | setfenv(f, getfenv())
138 | f()
139 | end
--------------------------------------------------------------------------------
/servers/www_console/lib/xterm.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4 | * https://github.com/chjj/term.js
5 | * @license MIT
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | *
25 | * Originally forked from (with the author's permission):
26 | * Fabrice Bellard's javascript vt100 for jslinux:
27 | * http://bellard.org/jslinux/
28 | * Copyright (c) 2011 Fabrice Bellard
29 | * The original design remains. The terminal itself
30 | * has been extended to include xterm CSI codes, among
31 | * other features.
32 | */
33 |
34 | /**
35 | * Default styles for xterm.js
36 | */
37 |
38 | .xterm {
39 | font-feature-settings: "liga" 0;
40 | position: relative;
41 | user-select: none;
42 | -ms-user-select: none;
43 | -webkit-user-select: none;
44 | }
45 |
46 | .xterm.focus,
47 | .xterm:focus {
48 | outline: none;
49 | }
50 |
51 | .xterm .xterm-helpers {
52 | position: absolute;
53 | top: 0;
54 | /**
55 | * The z-index of the helpers must be higher than the canvases in order for
56 | * IMEs to appear on top.
57 | */
58 | z-index: 5;
59 | }
60 |
61 | .xterm .xterm-helper-textarea {
62 | /*
63 | * HACK: to fix IE's blinking cursor
64 | * Move textarea out of the screen to the far left, so that the cursor is not visible.
65 | */
66 | position: absolute;
67 | opacity: 0;
68 | left: -9999em;
69 | top: 0;
70 | width: 0;
71 | height: 0;
72 | z-index: -5;
73 | /** Prevent wrapping so the IME appears against the textarea at the correct position */
74 | white-space: nowrap;
75 | overflow: hidden;
76 | resize: none;
77 | }
78 |
79 | .xterm .composition-view {
80 | /* TODO: Composition position got messed up somewhere */
81 | background: #000;
82 | color: #FFF;
83 | display: none;
84 | position: absolute;
85 | white-space: nowrap;
86 | z-index: 1;
87 | }
88 |
89 | .xterm .composition-view.active {
90 | display: block;
91 | }
92 |
93 | .xterm .xterm-viewport {
94 | /* On OS X this is required in order for the scroll bar to appear fully opaque */
95 | background-color: #000;
96 | overflow-y: scroll;
97 | cursor: default;
98 | position: absolute;
99 | right: 0;
100 | left: 0;
101 | top: 0;
102 | bottom: 0;
103 | }
104 |
105 | .xterm .xterm-screen {
106 | position: relative;
107 | }
108 |
109 | .xterm .xterm-screen canvas {
110 | position: absolute;
111 | left: 0;
112 | top: 0;
113 | }
114 |
115 | .xterm .xterm-scroll-area {
116 | visibility: hidden;
117 | }
118 |
119 | .xterm-char-measure-element {
120 | display: inline-block;
121 | visibility: hidden;
122 | position: absolute;
123 | top: 0;
124 | left: -9999em;
125 | line-height: normal;
126 | }
127 |
128 | .xterm {
129 | cursor: text;
130 | }
131 |
132 | .xterm.enable-mouse-events {
133 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
134 | cursor: default;
135 | }
136 |
137 | .xterm.xterm-cursor-pointer {
138 | cursor: pointer;
139 | }
140 |
141 | .xterm.column-select.focus {
142 | /* Column selection mode */
143 | cursor: crosshair;
144 | }
145 |
146 | .xterm .xterm-accessibility,
147 | .xterm .xterm-message {
148 | position: absolute;
149 | left: 0;
150 | top: 0;
151 | bottom: 0;
152 | right: 0;
153 | z-index: 10;
154 | color: transparent;
155 | }
156 |
157 | .xterm .live-region {
158 | position: absolute;
159 | left: -9999px;
160 | width: 1px;
161 | height: 1px;
162 | overflow: hidden;
163 | }
164 |
165 | .xterm-dim {
166 | opacity: 0.5;
167 | }
168 |
169 | .xterm-underline {
170 | text-decoration: underline;
171 | }
172 |
--------------------------------------------------------------------------------
/servers/main_twitch.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const ws = require('ws');
3 | const tmi = require('tmi.js');
4 | const fs = require('fs');
5 |
6 | // eh...
7 | const CHANNEL = process.argv[2] || process.env.CHANNEL_NAME;
8 |
9 | const WS_PORT = 9090;
10 | const SECS_BETWEEN_VOTES = process.env.SECS_BETWEEN_VOTES || 10;
11 | const SECS_FOR_VOTE = process.env.SECS_FOR_VOTE || 60;
12 |
13 | const wss = new ws.Server({ port: WS_PORT });
14 | console.log("WS listening on " + WS_PORT);
15 |
16 | let noita = null;
17 |
18 | function getConnectionName(ws) {
19 | return ws._socket.remoteAddress + ":" + ws._socket.remotePort;
20 | }
21 |
22 | function isConnectionLocalhost(ws) {
23 | const addr = ws._socket.remoteAddress
24 | return (addr == "::1") || (addr == "127.0.0.1") || (addr == "localhost") || (addr == "::ffff:127.0.0.1")
25 | }
26 |
27 | function noitaDoFile(fn) {
28 | if (noita == null) {
29 | return;
30 | }
31 | const fdata = fs.readFileSync(fn);
32 | noita.send(fdata);
33 | }
34 |
35 | // TODO: refactor all this into a common file
36 | wss.on('connection', function connection(ws) {
37 | const cname = getConnectionName(ws);
38 | console.log("New connection: " + cname);
39 | if (!isConnectionLocalhost(ws)) {
40 | console.log("Connection refused: not localhost!");
41 | ws.terminate();
42 | return
43 | }
44 |
45 | ws.on('message', function incoming(data, flags) {
46 | let jdata = null;
47 | // special case: if the string we get is prefixed with ">",
48 | // don't try to interpret as JSON, just treat it as a print
49 | if (data.slice(0, 1) == ">") {
50 | console.log(data);
51 | return;
52 | } else {
53 | try {
54 | jdata = JSON.parse(data);
55 | } catch (e) {
56 | console.log(data);
57 | console.error(e);
58 | return;
59 | }
60 | }
61 |
62 | if (jdata["kind"] === "heartbeat") {
63 | if (noita != ws) {
64 | console.log("Registering noita!");
65 | noita = ws;
66 | ws.send(`GamePrint('WS connected as ${cname}')`);
67 | ws.send("set_print_to_socket(true)");
68 | noitaDoFile("twitch_fragments/setup.lua");
69 | noitaDoFile("twitch_fragments/potion_material.lua");
70 | noitaDoFile("twitch_fragments/outcomes.lua");
71 | }
72 | }
73 | });
74 |
75 | ws.on('close', function closed(_ws, code, reason) {
76 | console.log("Connection closed: " + code + ", " + reason);
77 | if (ws === noita) {
78 | console.log("Noita closed");
79 | noita = null;
80 | }
81 | });
82 | });
83 |
84 | let timeleft = 0;
85 | let user_votes = {};
86 |
87 | function getVotes() {
88 | let votes = [0, 0, 0, 0];
89 | for(uname in user_votes) {
90 | let v = user_votes[uname];
91 | if (v >= 1 && v <= 4) {
92 | votes[v-1] += 1;
93 | }
94 | }
95 | return votes;
96 | }
97 |
98 | let accepting_votes = false;
99 | function handleVote(username, v) {
100 | if(!accepting_votes) {
101 | return;
102 | }
103 | let iv = parseInt(v.slice(0,1));
104 | if(isNaN(iv)) {
105 | return;
106 | }
107 | user_votes[username] = iv;
108 | }
109 |
110 | function sleep(nsecs) {
111 | return new Promise(resolve => setTimeout(resolve, nsecs * 1000));
112 | }
113 |
114 | async function doQuestion() {
115 | // main timer
116 | timeleft = SECS_BETWEEN_VOTES;
117 | while(timeleft > 0) {
118 | if (noita == null) {
119 | return;
120 | }
121 | noita.send(`set_countdown(${timeleft})`);
122 | timeleft -= 1;
123 | await sleep(1);
124 | }
125 | if (noita == null) {
126 | return;
127 | }
128 | accepting_votes = true;
129 | user_votes = {};
130 | noita.send("clear_display()\ndraw_outcomes(4)");
131 | timeleft = SECS_FOR_VOTE;
132 | while(timeleft > 0) {
133 | if (noita == null) {
134 | return;
135 | }
136 | noita.send(`set_votes{${getVotes().join(",")}}`);
137 | noita.send(`update_outcome_display(${timeleft})`);
138 | timeleft -= 1;
139 | await sleep(1);
140 | }
141 | if (noita == null) {
142 | return;
143 | }
144 | noita.send("do_winner()");
145 | }
146 |
147 | async function gameLoop() {
148 | while(true) {
149 | accepting_votes = false;
150 | while(noita == null) {
151 | console.log("Waiting for Noita...");
152 | await sleep(5);
153 | }
154 | await doQuestion();
155 | }
156 | }
157 |
158 | const options = {
159 | options: {debug: true},
160 | connection: {reconnect: true}
161 | //identity: { username: process.env.TWITCH_USERNAME, password: process.env.TWITCH_OAUTH}
162 | };
163 |
164 | const chatClient = new tmi.client(options);
165 | chatClient.connect().then((_client) => {
166 | chatClient.join(CHANNEL);
167 |
168 | chatClient.on("message", function (channel, userstate, message, self) {
169 | // console.log(message);
170 | // console.log(userstate);
171 | if (self) return; // Don't listen to my own messages..
172 | if (userstate["message-type"] === "chat") {
173 | handleVote(userstate['user-id'], message);
174 | }
175 | });
176 | });
177 |
178 | gameLoop();
--------------------------------------------------------------------------------
/mod_ws_api/data/ws/coroutines.lua:
--------------------------------------------------------------------------------
1 | if async then return end -- guard against multiple "includes"
2 |
3 | -- coroutine runtime ---------------------------------------------------------
4 | -- FIXME: stolen from https://github.com/ecbambrick/UntitledGame/tree/master/src/lib
5 |
6 |
7 | -- This file implements wait, wait_signal, signal, and their supporting stuff.
8 |
9 | -- This table is indexed by coroutine and simply contains the time at which the coroutine
10 | -- should be woken up.
11 | local WAITING_ON_TIME = {}
12 |
13 | -- This table is indexed by signal and contains list of coroutines that are waiting
14 | -- on a given signal
15 | local WAITING_ON_SIGNAL = {}
16 | local WAITING_ON_SIGNAL_CALLBACK = {}
17 |
18 | -- Keep track of how long the game has been running.
19 | local CURRENT_TIME = 0
20 |
21 | function wait(frames)
22 | -- Grab a reference to the current running coroutine
23 | local co = coroutine.running()
24 |
25 | -- If co is nil, that means we're on the main process, which isn't a coroutine and can't yield
26 | assert(co ~= nil, "The main thread cannot wait!")
27 |
28 | -- Store the coroutine and its wakeup time in the WAITING_ON_TIME table
29 | local wakeupTime = CURRENT_TIME + frames
30 | WAITING_ON_TIME[co] = wakeupTime
31 |
32 | -- And suspend the process
33 | return coroutine.yield(co)
34 | end
35 |
36 | function wake_up_waiting_threads(frames_delta)
37 | -- this function should be called once per game logic update with the amount of time
38 | -- that has passed since it was last called
39 | CURRENT_TIME = CURRENT_TIME + frames_delta
40 |
41 | local threadsToWake = {}
42 | for co, wakeupTime in pairs(WAITING_ON_TIME) do
43 | if wakeupTime < CURRENT_TIME then
44 | table.insert(threadsToWake, co)
45 | end
46 | end
47 |
48 | for _, co in ipairs(threadsToWake) do
49 | WAITING_ON_TIME[co] = nil -- setting a field to nil removes it from the table
50 | local happy, err = coroutine.resume(co)
51 | if not happy then (cprint or print)("Coroutine error: " .. tostring(err)) end
52 | end
53 | end
54 |
55 | function wait_signal(signalName)
56 | -- Same check as in wait; the main thread cannot wait
57 | local co = coroutine.running()
58 | assert(co ~= nil, "The main thread cannot wait!")
59 |
60 | if WAITING_ON_SIGNAL[signalStr] == nil then
61 | -- If there wasn't already a list for this signal, start a new one.
62 | WAITING_ON_SIGNAL[signalName] = { co }
63 | else
64 | table.insert(WAITING_ON_SIGNAL[signalName], co)
65 | end
66 |
67 | return coroutine.yield()
68 | end
69 |
70 | function wait_signal_callback(signalName, callback)
71 | -- Same check as in wait; the main thread cannot wait
72 | local co = coroutine.running()
73 | assert(co ~= nil, "The main thread cannot wait!")
74 |
75 | if WAITING_ON_SIGNAL[signalStr] == nil then
76 | -- If there wasn't already a list for this signal, start a new one.
77 | WAITING_ON_SIGNAL[signalName] = { co }
78 | else
79 | table.insert(WAITING_ON_SIGNAL[signalName], co)
80 | end
81 |
82 | if WAITING_ON_SIGNAL_CALLBACK[signalStr] == nil then
83 | -- If there wasn't already a list for this signal, start a new one.
84 | WAITING_ON_SIGNAL_CALLBACK[signalName] = { callback }
85 | else
86 | table.insert(WAITING_ON_SIGNAL_CALLBACK[signalName], callback)
87 | end
88 |
89 | return coroutine.yield()
90 | end
91 |
92 | function signal(signalName)
93 | local threads = WAITING_ON_SIGNAL[signalName]
94 | if threads == nil then return end
95 |
96 | -- call the callback
97 | local funcs = WAITING_ON_SIGNAL_CALLBACK[signalName]
98 | if funcs ~= nil then
99 | WAITING_ON_SIGNAL_CALLBACK[signalName] = nil
100 | for _, func in ipairs(funcs) do
101 | func()
102 | end
103 | end
104 |
105 | -- resume the coroutine
106 | WAITING_ON_SIGNAL[signalName] = nil
107 | for _, co in ipairs(threads) do
108 | coroutine.resume(co)
109 | end
110 | end
111 |
112 | function signal_2params(signalName, a, b)
113 | local threads = WAITING_ON_SIGNAL[signalName]
114 | if threads == nil then return end
115 |
116 | -- call the callback
117 | local funcs = WAITING_ON_SIGNAL_CALLBACK[signalName]
118 | if funcs ~= nil then
119 | WAITING_ON_SIGNAL_CALLBACK[signalName] = nil
120 | for _, func in ipairs(funcs) do
121 | func()
122 | end
123 | end
124 |
125 | -- resume the coroutine
126 | WAITING_ON_SIGNAL[signalName] = nil
127 | for _, co in ipairs(threads) do
128 | coroutine.resume(co, a, b)
129 | end
130 | end
131 |
132 |
133 | function async(func)
134 | -- This function is just a quick wrapper to start a coroutine.
135 | local co = coroutine.create(func)
136 | return coroutine.resume(co)
137 | end
138 |
139 | function async_loop(func)
140 | -- This function is just a quick wrapper to start a coroutine.
141 | local func_loop = function()
142 | while true do
143 | func()
144 | end
145 | end
146 |
147 | local co = coroutine.create(func_loop)
148 | return coroutine.resume(co)
149 | end
--------------------------------------------------------------------------------
/servers/www_console/js/noitaconsole.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | initCodeMirror();
3 | initConnection();
4 | });
5 |
6 | var codeWindow = null;
7 | var connection = null;
8 | var connected = false;
9 | var repl = null;
10 | var lineWindow = null;
11 | var fit = null;
12 |
13 | var commandHistory = [];
14 |
15 | function ansiRGB(r, g, b) {
16 | return `\x1b[38;2;${r};${g};${b}m`
17 | }
18 |
19 | function ansiBgRGB(r, g, b) {
20 | return `\x1b[48;2;${r};${g};${b}m`
21 | }
22 |
23 | function ansiReset() {
24 | return '\x1b[0m'
25 | }
26 |
27 |
28 | function getToken() {
29 | var parts = document.URL.split("/");
30 | if (parts[parts.length - 1].length == 32) {
31 | return parts[parts.length - 1]
32 | } else {
33 | return parts[parts.length - 2]
34 | }
35 | }
36 |
37 | function initConnection(url) {
38 | if(!url) {
39 | url = "ws://localhost:9090"
40 | }
41 | console.log("Connecting to url " + url);
42 |
43 | connection = new WebSocket(url);
44 | connection.addEventListener('open', function (event) {
45 | console.log('Connected: ' + url);
46 | connected = true;
47 | connection.send(JSON.stringify({kind: 'heartbeat', source: 'console', token: getToken()}));
48 | });
49 |
50 | connection.addEventListener('message', function (event) {
51 | var jdata = JSON.parse(event.data);
52 | if (jdata["kind"] === "print") {
53 | replPrint(jdata["text"]);
54 | } else {
55 | console.log("Unknown message: " + event.data)
56 | }
57 | });
58 | }
59 |
60 | function remoteEval(code) {
61 | if(code[0] === '!') {
62 | let parts = code.slice(1).split(" ");
63 | if(parts[0] === "connect") {
64 | initConnection(parts[1]);
65 | } else {
66 | replPrint("Unknown local command: " + parts[0]);
67 | }
68 | } else if(connected) {
69 | connection.send(JSON.stringify({kind: "command", command: code}));
70 | }
71 | }
72 |
73 | function longestSharedPrefix(a, b) {
74 | let nchars = Math.min(a.length, b.length);
75 | let prefixLength = 0;
76 | for(prefixLength = 0; prefixLength < nchars; ++prefixLength) {
77 | if(a[prefixLength] != b[prefixLength]) {
78 | break;
79 | }
80 | }
81 | return a.slice(0, prefixLength);
82 | }
83 |
84 | function longestSetPrefix(opts) {
85 | if(opts.length == 0) {
86 | return ""
87 | }
88 | let curPrefix = opts[0];
89 | for(let idx = 1; idx < opts.length; ++idx) {
90 | curPrefix = longestSharedPrefix(curPrefix, opts[idx]);
91 | }
92 | return curPrefix;
93 | }
94 |
95 | function replPrint(message) {
96 | message = message.replace(/\n/g, "\r\n");
97 | if(message.indexOf("ERR>") == 0) {
98 | message = ansiRGB(200, 0, 0) + message + ansiReset();
99 | } else if(message.indexOf("RES>") == 0) {
100 | message = ansiRGB(100, 100, 255) + message + ansiReset();
101 | } else if(message.indexOf("EVAL>") == 0) {
102 | message = ansiRGB(100, 255, 100) + message + ansiReset();
103 | } else if(message.indexOf("COM>") == 0) {
104 | // see how many suggestions we got
105 | let parts = message.slice(4).split(" ");
106 | let prefix = parts[0];
107 | let opts = parts[1].split(",");
108 | if(opts.length == 1 && opts[0] != "") {
109 | // fill in this completion
110 | lineWindow.setValue(prefix + opts[0]);
111 | lineWindow.setCursor(lineWindow.lineCount(), 0);
112 | } else if(opts.length > 1) {
113 | let completion = longestSetPrefix(opts);
114 | lineWindow.setValue(prefix + completion);
115 | lineWindow.setCursor(lineWindow.lineCount(), 0);
116 | }
117 | message = ansiRGB(150, 150, 150) + message + ansiReset();
118 | }
119 | repl.writeln(message);
120 | }
121 |
122 | function initCodeMirror() {
123 | codeWindow = CodeMirror.fromTextArea(document.getElementById("code"), {
124 | value: "-- Put multiline Lua stuff here\n",
125 | mode: "lua",
126 | theme: "dracula",
127 | lineNumbers: true
128 | });
129 |
130 | codeWindow.setOption("extraKeys", {
131 | "Shift-Enter": function(cm) {
132 | remoteEval(cm.getValue());
133 | replPrint("EVAL> [buffer]");
134 | }
135 | });
136 |
137 | lineWindow = CodeMirror.fromTextArea(document.getElementById("replinput"), {
138 | value: "",
139 | mode: "lua",
140 | theme: "dracula"
141 | });
142 |
143 | lineWindow.setOption("extraKeys", {
144 | "Enter": function(cm) {
145 | let val = cm.getValue();
146 | if(val == "") {
147 | return;
148 | }
149 | if(val.slice(-1) == "?") {
150 | val = 'help("' + val.slice(0,-1) + '")'
151 | }
152 | remoteEval(val);
153 | if(commandHistory.indexOf(val) < 0) {
154 | commandHistory.push(val);
155 | }
156 | replPrint("EVAL> " + val);
157 | cm.setValue("");
158 | },
159 | "Tab": function(cm) {
160 | console.log("TAB");
161 | const val = cm.getValue().trim();
162 | if(val == "") {
163 | return;
164 | }
165 | // Note that [=[ some string ]=] is a special Lua
166 | // string literal that allows nesting of other string
167 | // literals, including the more typical [[ ]] pair
168 | remoteEval(`complete([=[${val}]=])`);
169 | },
170 | "Up": function(cm) {
171 | let hpos = commandHistory.indexOf(cm.getValue());
172 | if(hpos > 0) {
173 | cm.setValue(commandHistory[hpos-1]);
174 | } else if(hpos == 0) {
175 | // don't do anything
176 | } else {
177 | cm.setValue(commandHistory[commandHistory.length-1]);
178 | }
179 | },
180 | "Down": function(cm) {
181 | let hpos = commandHistory.indexOf(cm.getValue());
182 | if(hpos >= 0 && hpos < commandHistory.length - 1) {
183 | cm.setValue(commandHistory[hpos+1]);
184 | }
185 | }
186 | });
187 |
188 | repl = new Terminal({
189 | theme: {
190 | background: '#111'
191 | }
192 | });
193 | fit = new FitAddon.FitAddon();
194 | repl.loadAddon(fit);
195 | repl.open(document.getElementById("repl"));
196 | repl._initialized = true;
197 |
198 | fit.fit();
199 |
200 | repl.writeln('Noita console');
201 | repl.writeln('(Note that this panel is for output only)');
202 | repl.writeln('');
203 | }
204 |
--------------------------------------------------------------------------------
/servers/www_console/lib/modes/lua/lua.js:
--------------------------------------------------------------------------------
1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 | // Distributed under an MIT license: https://codemirror.net/LICENSE
3 |
4 | // LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's
5 | // CodeMirror 1 mode.
6 | // highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting
7 |
8 | (function(mod) {
9 | if (typeof exports == "object" && typeof module == "object") // CommonJS
10 | mod(require("../../lib/codemirror"));
11 | else if (typeof define == "function" && define.amd) // AMD
12 | define(["../../lib/codemirror"], mod);
13 | else // Plain browser env
14 | mod(CodeMirror);
15 | })(function(CodeMirror) {
16 | "use strict";
17 |
18 | CodeMirror.defineMode("lua", function(config, parserConfig) {
19 | var indentUnit = config.indentUnit;
20 |
21 | function prefixRE(words) {
22 | return new RegExp("^(?:" + words.join("|") + ")", "i");
23 | }
24 | function wordRE(words) {
25 | return new RegExp("^(?:" + words.join("|") + ")$", "i");
26 | }
27 | var specials = wordRE(parserConfig.specials || []);
28 |
29 | // long list of standard functions from lua manual
30 | var builtins = wordRE([
31 | "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load",
32 | "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require",
33 | "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall",
34 |
35 | "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield",
36 |
37 | "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable",
38 | "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable",
39 | "debug.setupvalue","debug.traceback",
40 |
41 | "close","flush","lines","read","seek","setvbuf","write",
42 |
43 | "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin",
44 | "io.stdout","io.tmpfile","io.type","io.write",
45 |
46 | "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg",
47 | "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max",
48 | "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh",
49 | "math.sqrt","math.tan","math.tanh",
50 |
51 | "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale",
52 | "os.time","os.tmpname",
53 |
54 | "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload",
55 | "package.seeall",
56 |
57 | "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub",
58 | "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper",
59 |
60 | "table.concat","table.insert","table.maxn","table.remove","table.sort"
61 | ]);
62 | var keywords = wordRE(["and","break","elseif","false","nil","not","or","return",
63 | "true","function", "end", "if", "then", "else", "do",
64 | "while", "repeat", "until", "for", "in", "local" ]);
65 |
66 | var indentTokens = wordRE(["function", "if","repeat","do", "\\(", "{"]);
67 | var dedentTokens = wordRE(["end", "until", "\\)", "}"]);
68 | var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]);
69 |
70 | function readBracket(stream) {
71 | var level = 0;
72 | while (stream.eat("=")) ++level;
73 | stream.eat("[");
74 | return level;
75 | }
76 |
77 | function normal(stream, state) {
78 | var ch = stream.next();
79 | if (ch == "-" && stream.eat("-")) {
80 | if (stream.eat("[") && stream.eat("["))
81 | return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state);
82 | stream.skipToEnd();
83 | return "comment";
84 | }
85 | if (ch == "\"" || ch == "'")
86 | return (state.cur = string(ch))(stream, state);
87 | if (ch == "[" && /[\[=]/.test(stream.peek()))
88 | return (state.cur = bracketed(readBracket(stream), "string"))(stream, state);
89 | if (/\d/.test(ch)) {
90 | stream.eatWhile(/[\w.%]/);
91 | return "number";
92 | }
93 | if (/[\w_]/.test(ch)) {
94 | stream.eatWhile(/[\w\\\-_.]/);
95 | return "variable";
96 | }
97 | return null;
98 | }
99 |
100 | function bracketed(level, style) {
101 | return function(stream, state) {
102 | var curlev = null, ch;
103 | while ((ch = stream.next()) != null) {
104 | if (curlev == null) {if (ch == "]") curlev = 0;}
105 | else if (ch == "=") ++curlev;
106 | else if (ch == "]" && curlev == level) { state.cur = normal; break; }
107 | else curlev = null;
108 | }
109 | return style;
110 | };
111 | }
112 |
113 | function string(quote) {
114 | return function(stream, state) {
115 | var escaped = false, ch;
116 | while ((ch = stream.next()) != null) {
117 | if (ch == quote && !escaped) break;
118 | escaped = !escaped && ch == "\\";
119 | }
120 | if (!escaped) state.cur = normal;
121 | return "string";
122 | };
123 | }
124 |
125 | return {
126 | startState: function(basecol) {
127 | return {basecol: basecol || 0, indentDepth: 0, cur: normal};
128 | },
129 |
130 | token: function(stream, state) {
131 | if (stream.eatSpace()) return null;
132 | var style = state.cur(stream, state);
133 | var word = stream.current();
134 | if (style == "variable") {
135 | if (keywords.test(word)) style = "keyword";
136 | else if (builtins.test(word)) style = "builtin";
137 | else if (specials.test(word)) style = "variable-2";
138 | }
139 | if ((style != "comment") && (style != "string")){
140 | if (indentTokens.test(word)) ++state.indentDepth;
141 | else if (dedentTokens.test(word)) --state.indentDepth;
142 | }
143 | return style;
144 | },
145 |
146 | indent: function(state, textAfter) {
147 | var closing = dedentPartial.test(textAfter);
148 | return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0));
149 | },
150 |
151 | lineComment: "--",
152 | blockCommentStart: "--[[",
153 | blockCommentEnd: "]]"
154 | };
155 | });
156 |
157 | CodeMirror.defineMIME("text/x-lua", "lua");
158 |
159 | });
160 |
--------------------------------------------------------------------------------
/mod_ws_api/data/ws/ws.lua:
--------------------------------------------------------------------------------
1 | dofile("data/ws/pollws.lua")
2 | dofile("data/ws/host.lua")
3 | dofile("data/ws/coroutines.lua")
4 |
5 | -- this empty table is used as a special value that will suppress
6 | -- printing any kind of "RES>" value (normally "[no value]" would print)
7 | local UNPRINTABLE_RESULT = {}
8 |
9 | -- (from http://lua-users.org/wiki/SplitJoin)
10 | local strfind = string.find
11 | local tinsert = table.insert
12 | local strsub = string.sub
13 | local function strsplit(text, delimiter)
14 | local list = {}
15 | local pos = 1
16 | if strfind("", delimiter, 1) then -- this would result in endless loops
17 | error("Delimiter matches empty string!")
18 | end
19 | while 1 do
20 | local first, last = strfind(text, delimiter, pos)
21 | if first then -- found?
22 | tinsert(list, strsub(text, pos, first-1))
23 | pos = last+1
24 | else
25 | tinsert(list, strsub(text, pos))
26 | break
27 | end
28 | end
29 | return list
30 | end
31 |
32 | local main_socket = nil
33 |
34 | local function reconnect(url)
35 | url = url or get_ws_host_url() -- comes from data/ws/host.lua
36 | if not url then return false end
37 | GamePrint("ws-api: trying to connect to " .. url)
38 | main_socket = open_socket(url)
39 | main_socket:poll()
40 | end
41 |
42 | local printing_to_socket = false
43 |
44 | local function set_scratch_size(scratch_size)
45 | if not scratch_size then error("Scratch size cannot be nil") end
46 | main_socket:set_scratch_size(scratch_size)
47 | end
48 |
49 | local function socket_send(msg)
50 | main_socket:send(msg)
51 | end
52 |
53 | local function socket_print(str)
54 | local msg = ">" .. str
55 | main_socket:send(msg)
56 | end
57 |
58 | function cprint(...)
59 | local m = table.concat({...}, " ")
60 | if main_socket and printing_to_socket then
61 | socket_print(m)
62 | else
63 | print(m)
64 | end
65 | end
66 |
67 | local function set_print_to_socket(p)
68 | printing_to_socket = p
69 | end
70 |
71 | local function cprint_table(t)
72 | local s = {}
73 | for k, v in pairs(t) do
74 | table.insert(s, k .. ": " .. type(v))
75 | end
76 | cprint(table.concat(s, "\n"))
77 | end
78 |
79 | local console_env = nil
80 |
81 | local function reload_utils()
82 | local env_utils, err = loadfile("data/ws/utils.lua")
83 | if type(env_utils) ~= "function" then
84 | cprint("Error loading utils: " .. tostring(err))
85 | return
86 | end
87 | setfenv(env_utils, console_env)
88 | local happy, err = pcall(env_utils)
89 | if not happy then
90 | cprint("Error executing utils: " .. tostring(err))
91 | end
92 | cprint("Utils loaded.")
93 | end
94 |
95 | local _persistent_funcs = {}
96 |
97 | local function add_persistent_func(name, f)
98 | _persistent_funcs[name] = f
99 | end
100 |
101 | local function remove_persistent_func(name)
102 | _persistent_funcs[name] = nil
103 | end
104 |
105 | local function run_persistent_funcs()
106 | for fname, f in pairs(_persistent_funcs) do
107 | local happy, err = pcall(f, fname)
108 | if not happy then cprint(err) end
109 | end
110 | end
111 |
112 | local function _dofile(fn)
113 | local s = loadfile(fn)
114 | if type(s) == 'string' then
115 | -- work around Noita's broken loadfile that returns error
116 | -- message as first argument rather than as second
117 | error(fn .. ": " .. s)
118 | end
119 | setfenv(s, console_env)
120 | return s()
121 | end
122 |
123 | local _help_info = nil
124 | local function reload_help(fn)
125 | fn = fn or "tools_modding/lua_api_documentation.txt"
126 | local f, err = io.open(fn)
127 | if not f then error("Couldn't open " .. fn) end
128 | local res = f:read("*a")
129 | f:close()
130 | if not res then error("Couldn't read " .. fn) end
131 | _help_info = {}
132 | res = res:gsub("\r", "") -- get rid of horrible carriage returns
133 | local lines = strsplit(res, "\n")
134 | for _, line in ipairs(lines) do
135 | local paren_idx = line:find("%(")
136 | if paren_idx then
137 | local funcname = line:sub(1, paren_idx-1)
138 | _help_info[funcname] = line
139 | end
140 | end
141 | end
142 |
143 | local function help_str(funcname)
144 | if not _help_info then reload_help() end
145 | return _help_info[funcname]
146 | end
147 |
148 | local function help(funcname)
149 | cprint(help_str(funcname) or (funcname .. "-> [no help available]"))
150 | return UNPRINTABLE_RESULT
151 | end
152 |
153 | local function _strinfo(v)
154 | if v == nil then return "nil" end
155 | local vtype = type(v)
156 | if vtype == "number" then
157 | return ("%0.4f"):format(v)
158 | elseif vtype == "string" then
159 | return '"' .. v .. '"'
160 | elseif vtype == "boolean" then
161 | return tostring(v)
162 | else
163 | return ("[%s] %s"):format(vtype, tostring(v))
164 | end
165 | end
166 |
167 | local function strinfo(...)
168 | local frags = {}
169 | local nargs = select('#', ...)
170 | if nargs == 0 then
171 | return "[no value]"
172 | end
173 | if nargs == 1 and select(1, ...) == UNPRINTABLE_RESULT then
174 | return UNPRINTABLE_RESULT
175 | end
176 | for idx = 1, nargs do
177 | frags[idx] = _strinfo(select(idx, ...))
178 | end
179 | return table.concat(frags, ", ")
180 | end
181 |
182 | local function info(...)
183 | cprint(strinfo(...))
184 | return UNPRINTABLE_RESULT
185 | end
186 |
187 | local function complete(s)
188 | local opts = {}
189 |
190 | local parts = strsplit(s, "%.") -- strsplit takes a pattern, so have to escape "."
191 | local cur = console_env
192 | local prefix = ""
193 | for idx = 1, (#parts) - 1 do
194 | cur = cur[parts[idx]]
195 | if not cur then return UNPRINTABLE_RESULT end
196 | prefix = prefix .. parts[idx] .. "."
197 | end
198 | if type(cur) ~= "table" then return UNPRINTABLE_RESULT end
199 | local lastpart = parts[#parts]
200 | if not lastpart then return UNPRINTABLE_RESULT end
201 | for k, _ in pairs(cur) do
202 | if k:find(lastpart) == 1 then
203 | table.insert(opts, k)
204 | end
205 | end
206 | if #opts > 0 then
207 | table.sort(opts)
208 | cprint("COM>" .. prefix .. " " .. table.concat(opts, ","))
209 | end
210 | return UNPRINTABLE_RESULT
211 | end
212 |
213 | local function new_console_env()
214 | console_env = {}
215 | for k, v in pairs(getfenv()) do
216 | console_env[k] = v
217 | end
218 | console_env.reconnect = reconnect
219 | console_env.complete = complete
220 | console_env.new_console_env = new_console_env
221 | console_env.set_print_to_socket = set_print_to_socket
222 | console_env.print = cprint
223 | console_env.print_table = cprint_table
224 | console_env.socket_print = socket_print
225 | console_env.socket_send = socket_send
226 | console_env.rawprint = print
227 | console_env.reload_utils = reload_utils
228 | console_env.add_persistent_func = add_persistent_func
229 | console_env.set_persistent_func = add_persistent_func -- alias
230 | console_env.remove_persistent_func = remove_persistent_func
231 | console_env.strinfo = strinfo
232 | console_env.info = info
233 | console_env.dofile = _dofile
234 | console_env.reload_help = reload_help
235 | console_env.help_str = help_str
236 | console_env.help = help
237 | console_env.UNPRINTABLE_RESULT = UNPRINTABLE_RESULT
238 |
239 | reload_utils()
240 | end
241 | new_console_env()
242 |
243 | local function _collect(happy, ...)
244 | if happy then
245 | return happy, strinfo(...)
246 | else
247 | return happy, ...
248 | end
249 | end
250 |
251 | local function do_command(msg)
252 | local f, err = nil, nil
253 | if not msg:find("\n") then
254 | -- if this is a single line, try putting "return " in front
255 | -- (convenience to allow us to inspect values)
256 | f, err = loadstring("return " .. msg)
257 | end
258 | if not f then -- multiline, or not an expression
259 | f, err = loadstring(msg)
260 | if not f then
261 | cprint("ERR> Parse error: " .. tostring(err))
262 | return
263 | end
264 | end
265 | setfenv(f, console_env)
266 | local happy, retval = _collect(pcall(f))
267 | if happy then
268 | if retval ~= UNPRINTABLE_RESULT then
269 | cprint("RES> " .. tostring(retval))
270 | end
271 | else
272 | cprint("ERR> " .. tostring(retval))
273 | end
274 | end
275 |
276 | local count = 0
277 |
278 | _ws_main = function()
279 | if main_socket then
280 | local msg = main_socket:poll()
281 | if msg then
282 | do_command(msg)
283 | end
284 | if count % 60 == 0 then
285 | main_socket:send('{"kind": "heartbeat", "source": "noita"}')
286 | end
287 | run_persistent_funcs()
288 | elseif count % 60 == 0 then
289 | reconnect()
290 | end
291 | wake_up_waiting_threads(1) -- from coroutines.lua
292 | count = count + 1
293 | end
--------------------------------------------------------------------------------
/servers/twitch_fragments/outcomes.lua:
--------------------------------------------------------------------------------
1 | dofile("data/scripts/lib/utilities.lua")
2 | dofile("data/scripts/perks/perk.lua")
3 | dofile("data/scripts/perks/perk_list.lua")
4 |
5 | local function insert_constant(outcome)
6 | table.insert(outcome_generators, function()
7 | return outcome
8 | end)
9 | end
10 |
11 |
12 | table.insert(outcome_generators, function()
13 | -- extracted from data/scripts/items/potion.lua
14 | local materials = nil
15 | if( Random( 0, 100 ) <= 75 ) then
16 | if( Random( 0, 100000 ) <= 50 ) then
17 | potion_material = "magic_liquid_hp_regeneration"
18 | elseif( Random( 200, 100000 ) <= 250 ) then
19 | potion_material = "purifying_powder"
20 | else
21 | potion_material = random_from_array( potion_materials_magic )
22 | end
23 | else
24 | potion_material = random_from_array( potion_materials_standard )
25 | end
26 |
27 | local matname = GameTextGet("$mat_" .. potion_material)
28 |
29 | return {
30 | name = "Flask: " .. matname,
31 | desc = "Enjoy",
32 | func = function()
33 | local x, y = get_player_pos()
34 | -- just go ahead and assume cheatgui is installed
35 | local entity = EntityLoad( "data/hax/potion_empty.xml", x, y )
36 | AddMaterialInventoryMaterial( entity, potion_material, 1000 )
37 | end
38 | }
39 | end)
40 |
41 | local function resolve_localized_name(s, default)
42 | if s:sub(1,1) ~= "$" then return s end
43 | local rep = GameTextGet(s)
44 | if rep and rep ~= "" then return rep else return default or s end
45 | end
46 |
47 | table.insert(outcome_generators, function()
48 | -- spawn a random perk
49 | local perk = perk_list[math.random(1, #perk_list)]
50 |
51 | -- reroll useless perk
52 | while perk.id == "MYSTERY_EGGPLANT" do
53 | perk = perk_list[math.random(1, #perk_list)]
54 | end
55 |
56 | local perkname = resolve_localized_name(perk.ui_name, perk.id)
57 | return {
58 | name = "Perk: " .. perkname,
59 | desc = "Have fun",
60 | func = function()
61 | local x, y = get_player_pos()
62 |
63 | -- player might be dead
64 | if x ~= nil and y ~= nil then
65 | local perk_entity = perk_spawn( x, y - 8, perk.id )
66 | local players = get_players()
67 | if players == nil then return end
68 | for i,player in ipairs(players) do
69 | -- last argument set to false, so you dont kill others perks if triggered inside the shop
70 | perk_pickup( perk_entity, player, nil, true, false )
71 | end
72 | end
73 | end
74 | }
75 | end)
76 |
77 | local function twiddle_health(f)
78 | local damagemodels = EntityGetComponent( get_player(), "DamageModelComponent" )
79 | if( damagemodels ~= nil ) then
80 | for i,damagemodel in ipairs(damagemodels) do
81 | local max_hp = tonumber(ComponentGetValue( damagemodel, "max_hp"))
82 | local cur_hp = tonumber(ComponentGetValue( damagemodel, "hp"))
83 | local new_cur, new_max = f(cur_hp, max_hp)
84 | ComponentSetValue( damagemodel, "max_hp", new_max)
85 | ComponentSetValue( damagemodel, "hp", new_cur)
86 | end
87 | end
88 | end
89 |
90 | insert_constant{
91 | name = "Health Down",
92 | desc = "Unfortunate",
93 | func = function()
94 | twiddle_health(function(cur, max)
95 | max = max * 0.8
96 | cur = math.min(max, cur)
97 | return cur, max
98 | end)
99 | end
100 | }
101 |
102 | insert_constant{
103 | name = "Health Up",
104 | desc = "Amazing",
105 | func = function()
106 | twiddle_health(function(cur, max)
107 | return cur+1, max+1
108 | end)
109 | end
110 | }
111 |
112 | local function urand(mag)
113 | return math.floor((math.random()*2.0 - 1.0)*mag)
114 | end
115 |
116 | local function spawn_item(path, offset_mag)
117 | local x, y = get_player_pos()
118 | local dx, dy = urand(offset_mag or 0), urand(offset_mag or 0)
119 | print(x + dx, y + dy)
120 | local entity = EntityLoad(path, x + dx, y + dy)
121 | end
122 |
123 | local function wrap_spawn(path, offset_mag)
124 | return function() spawn_item(path, offset_mag) end
125 | end
126 |
127 | -- 0 to not limit axis, -1 to limit to negative values, 1 to limit to positive values
128 | local function generate_value_in_range(max_range, min_range, limit_axis)
129 | local range = (max_range or 0) - (min_range or 0)
130 | if (limit_axis or 0) == 0 then
131 | limit_axis = Random(0, 1) == 0 and 1 or -1
132 | end
133 |
134 | return (Random(0, range) + (min_range or 0)) * limit_axis
135 | end
136 |
137 | local function spawn_item_in_range(path, min_x_range, max_x_range, min_y_range, max_y_range, limit_x_axis, limit_y_axis, spawn_blackhole)
138 | local x, y = get_player_pos()
139 | local dx = generate_value_in_range(max_x_range, min_x_range, limit_x_axis)
140 | local dy = generate_value_in_range(max_y_range, min_y_range, limit_y_axis)
141 |
142 | if spawn_blackhole then
143 | EntityLoad("data/entities/projectiles/deck/black_hole.xml", x + dx, y + dy)
144 | end
145 |
146 | return EntityLoad(path, x + dx, y + dy)
147 | end
148 |
149 | local function spawn_item(path, min_range, max_range, spawn_blackhole)
150 | return spawn_item_in_range(path, min_range, max_range, min_range, max_range, 0, 0, spawn_blackhole)
151 | end
152 |
153 |
154 | insert_constant{
155 | name = "The Gods Are Angry",
156 | desc = "...",
157 | func = function()
158 | spawn_item("data/entities/animals/necromancer_shop.xml", 100, 100, true)
159 | end
160 | }
161 |
162 | insert_constant{
163 | name = "A big worm",
164 | desc = "This could be a problem",
165 | func = function()
166 | spawn_item("data/entities/animals/worm_big.xml", 100, 100)
167 | end
168 | }
169 |
170 | insert_constant{
171 | name = "The biggest worm",
172 | desc = "oh ... oh no ... OH NO NO NO",
173 | func = function()
174 | spawn_item("data/entities/animals/worm_end.xml", 100, 150)
175 | end
176 | }
177 |
178 | insert_constant{
179 | name = "A couple of worms",
180 | desc = "That's annoying",
181 | func = function()
182 | spawn_item("data/entities/animals/worm.xml", 50, 200)
183 | spawn_item("data/entities/animals/worm.xml", 50, 200)
184 | end
185 | }
186 |
187 | insert_constant{
188 | name = "A can of worms",
189 | desc = "But why?",
190 | func = function()
191 | for i=1,10 do
192 | spawn_item("data/entities/animals/worm_tiny.xml", 50, 200)
193 | end
194 | end
195 | }
196 |
197 | insert_constant{
198 | name = "Deers",
199 | desc = "Oh dear!",
200 | func = function()
201 | for i=1,5 do
202 | spawn_item("data/entities/projectiles/deck/exploding_deer.xml", 100, 300)
203 | spawn_item("data/entities/animals/deer.xml", 100, 300)
204 | end
205 | end
206 | }
207 |
208 |
209 | insert_constant{
210 | name = "Gold rush",
211 | desc = "Quick, before it disappear",
212 | func = function()
213 | for i=1,20 do
214 | spawn_item("data/entities/items/pickup/goldnugget.xml", 50, 200)
215 | end
216 | end
217 | }
218 |
219 | insert_constant{
220 | name = "Bomb rush",
221 | desc = "You better run",
222 | func = function()
223 | for i=1,3 do
224 | spawn_item("data/entities/projectiles/bomb.xml", 0, 50)
225 | end
226 | end
227 | }
228 |
229 | insert_constant{
230 | name = "Sea of lava",
231 | desc = "Now, that's hot!!",
232 | func = function()
233 | spawn_item_in_range("data/entities/projectiles/deck/sea_lava.xml", 0, 200, 20, 100, 0, 1, false)
234 | end
235 | }
236 |
237 | insert_constant{
238 | name = "ACID??",
239 | desc = "Who thought this was a good idea",
240 | func = function()
241 | spawn_item("data/entities/projectiles/deck/circle_acid.xml", 40, 120, true)
242 | end
243 | }
244 |
245 | insert_constant{
246 | name = "Holy bombastic",
247 | desc = "That should clear the path",
248 | func = function()
249 | spawn_item("data/entities/projectiles/bomb_holy.xml", 130, 250, true)
250 | end
251 | }
252 |
253 | insert_constant{
254 | name = "Thunderstone madness",
255 | desc = "Careful now",
256 | func = function()
257 | for i=1,10 do
258 | spawn_item("data/entities/items/pickup/thunderstone.xml", 50, 250)
259 | end
260 | end
261 | }
262 |
263 | insert_constant{
264 | name = "Instant swimming pool",
265 | desc = "Don't forget your swimsuit",
266 | func = function()
267 | spawn_item_in_range("data/entities/projectiles/deck/sea_water.xml", 0, 0, 40, 80, 0, -1, false)
268 | end
269 | }
270 |
271 | insert_constant{
272 | name = "Random wand generator",
273 | desc = "Make good use of it",
274 | func = function()
275 | local rnd = Random(0, 1000)
276 | if rnd < 200 then
277 | spawn_item("data/entities/items/wand_level_01.xml")
278 | elseif rnd < 600 then
279 | spawn_item("data/entities/items/wand_level_02.xml")
280 | elseif rnd < 850 then
281 | spawn_item("data/entities/items/wand_level_03.xml")
282 | elseif rnd < 998 then
283 | spawn_item("data/entities/items/wand_level_04.xml")
284 | else
285 | spawn_item("data/entities/items/wand_level_05.xml")
286 | end
287 | end
288 | }
289 |
--------------------------------------------------------------------------------
/servers/www_console/lib/codemirror.css:
--------------------------------------------------------------------------------
1 | /* BASICS */
2 |
3 | .CodeMirror {
4 | /* Set height, width, borders, and global font properties here */
5 | font-family: monospace;
6 | height: 100%;
7 | color: black;
8 | direction: ltr;
9 | }
10 |
11 | /* PADDING */
12 |
13 | .CodeMirror-lines {
14 | padding: 4px 0; /* Vertical padding around content */
15 | }
16 | .CodeMirror pre.CodeMirror-line,
17 | .CodeMirror pre.CodeMirror-line-like {
18 | padding: 0 4px; /* Horizontal padding of content */
19 | }
20 |
21 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
22 | background-color: white; /* The little square between H and V scrollbars */
23 | }
24 |
25 | /* GUTTER */
26 |
27 | .CodeMirror-gutters {
28 | border-right: 1px solid #ddd;
29 | background-color: #f7f7f7;
30 | white-space: nowrap;
31 | }
32 | .CodeMirror-linenumbers {}
33 | .CodeMirror-linenumber {
34 | padding: 0 3px 0 5px;
35 | min-width: 20px;
36 | text-align: right;
37 | color: #999;
38 | white-space: nowrap;
39 | }
40 |
41 | .CodeMirror-guttermarker { color: black; }
42 | .CodeMirror-guttermarker-subtle { color: #999; }
43 |
44 | /* CURSOR */
45 |
46 | .CodeMirror-cursor {
47 | border-left: 1px solid black;
48 | border-right: none;
49 | width: 0;
50 | }
51 | /* Shown when moving in bi-directional text */
52 | .CodeMirror div.CodeMirror-secondarycursor {
53 | border-left: 1px solid silver;
54 | }
55 | .cm-fat-cursor .CodeMirror-cursor {
56 | width: auto;
57 | border: 0 !important;
58 | background: #7e7;
59 | }
60 | .cm-fat-cursor div.CodeMirror-cursors {
61 | z-index: 1;
62 | }
63 | .cm-fat-cursor-mark {
64 | background-color: rgba(20, 255, 20, 0.5);
65 | -webkit-animation: blink 1.06s steps(1) infinite;
66 | -moz-animation: blink 1.06s steps(1) infinite;
67 | animation: blink 1.06s steps(1) infinite;
68 | }
69 | .cm-animate-fat-cursor {
70 | width: auto;
71 | border: 0;
72 | -webkit-animation: blink 1.06s steps(1) infinite;
73 | -moz-animation: blink 1.06s steps(1) infinite;
74 | animation: blink 1.06s steps(1) infinite;
75 | background-color: #7e7;
76 | }
77 | @-moz-keyframes blink {
78 | 0% {}
79 | 50% { background-color: transparent; }
80 | 100% {}
81 | }
82 | @-webkit-keyframes blink {
83 | 0% {}
84 | 50% { background-color: transparent; }
85 | 100% {}
86 | }
87 | @keyframes blink {
88 | 0% {}
89 | 50% { background-color: transparent; }
90 | 100% {}
91 | }
92 |
93 | /* Can style cursor different in overwrite (non-insert) mode */
94 | .CodeMirror-overwrite .CodeMirror-cursor {}
95 |
96 | .cm-tab { display: inline-block; text-decoration: inherit; }
97 |
98 | .CodeMirror-rulers {
99 | position: absolute;
100 | left: 0; right: 0; top: -50px; bottom: 0;
101 | overflow: hidden;
102 | }
103 | .CodeMirror-ruler {
104 | border-left: 1px solid #ccc;
105 | top: 0; bottom: 0;
106 | position: absolute;
107 | }
108 |
109 | /* DEFAULT THEME */
110 |
111 | .cm-s-default .cm-header {color: blue;}
112 | .cm-s-default .cm-quote {color: #090;}
113 | .cm-negative {color: #d44;}
114 | .cm-positive {color: #292;}
115 | .cm-header, .cm-strong {font-weight: bold;}
116 | .cm-em {font-style: italic;}
117 | .cm-link {text-decoration: underline;}
118 | .cm-strikethrough {text-decoration: line-through;}
119 |
120 | .cm-s-default .cm-keyword {color: #708;}
121 | .cm-s-default .cm-atom {color: #219;}
122 | .cm-s-default .cm-number {color: #164;}
123 | .cm-s-default .cm-def {color: #00f;}
124 | .cm-s-default .cm-variable,
125 | .cm-s-default .cm-punctuation,
126 | .cm-s-default .cm-property,
127 | .cm-s-default .cm-operator {}
128 | .cm-s-default .cm-variable-2 {color: #05a;}
129 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
130 | .cm-s-default .cm-comment {color: #a50;}
131 | .cm-s-default .cm-string {color: #a11;}
132 | .cm-s-default .cm-string-2 {color: #f50;}
133 | .cm-s-default .cm-meta {color: #555;}
134 | .cm-s-default .cm-qualifier {color: #555;}
135 | .cm-s-default .cm-builtin {color: #30a;}
136 | .cm-s-default .cm-bracket {color: #997;}
137 | .cm-s-default .cm-tag {color: #170;}
138 | .cm-s-default .cm-attribute {color: #00c;}
139 | .cm-s-default .cm-hr {color: #999;}
140 | .cm-s-default .cm-link {color: #00c;}
141 |
142 | .cm-s-default .cm-error {color: #f00;}
143 | .cm-invalidchar {color: #f00;}
144 |
145 | .CodeMirror-composing { border-bottom: 2px solid; }
146 |
147 | /* Default styles for common addons */
148 |
149 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
150 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
151 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
152 | .CodeMirror-activeline-background {background: #e8f2ff;}
153 |
154 | /* STOP */
155 |
156 | /* The rest of this file contains styles related to the mechanics of
157 | the editor. You probably shouldn't touch them. */
158 |
159 | .CodeMirror {
160 | position: relative;
161 | overflow: hidden;
162 | background: white;
163 | }
164 |
165 | .CodeMirror-scroll {
166 | overflow: scroll !important; /* Things will break if this is overridden */
167 | /* 30px is the magic margin used to hide the element's real scrollbars */
168 | /* See overflow: hidden in .CodeMirror */
169 | margin-bottom: -30px; margin-right: -30px;
170 | padding-bottom: 30px;
171 | height: 100%;
172 | outline: none; /* Prevent dragging from highlighting the element */
173 | position: relative;
174 | }
175 | .CodeMirror-sizer {
176 | position: relative;
177 | border-right: 30px solid transparent;
178 | }
179 |
180 | /* The fake, visible scrollbars. Used to force redraw during scrolling
181 | before actual scrolling happens, thus preventing shaking and
182 | flickering artifacts. */
183 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
184 | position: absolute;
185 | z-index: 6;
186 | display: none;
187 | }
188 | .CodeMirror-vscrollbar {
189 | right: 0; top: 0;
190 | overflow-x: hidden;
191 | overflow-y: scroll;
192 | }
193 | .CodeMirror-hscrollbar {
194 | bottom: 0; left: 0;
195 | overflow-y: hidden;
196 | overflow-x: scroll;
197 | }
198 | .CodeMirror-scrollbar-filler {
199 | right: 0; bottom: 0;
200 | }
201 | .CodeMirror-gutter-filler {
202 | left: 0; bottom: 0;
203 | }
204 |
205 | .CodeMirror-gutters {
206 | position: absolute; left: 0; top: 0;
207 | min-height: 100%;
208 | z-index: 3;
209 | }
210 | .CodeMirror-gutter {
211 | white-space: normal;
212 | height: 100%;
213 | display: inline-block;
214 | vertical-align: top;
215 | margin-bottom: -30px;
216 | }
217 | .CodeMirror-gutter-wrapper {
218 | position: absolute;
219 | z-index: 4;
220 | background: none !important;
221 | border: none !important;
222 | }
223 | .CodeMirror-gutter-background {
224 | position: absolute;
225 | top: 0; bottom: 0;
226 | z-index: 4;
227 | }
228 | .CodeMirror-gutter-elt {
229 | position: absolute;
230 | cursor: default;
231 | z-index: 4;
232 | }
233 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent }
234 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
235 |
236 | .CodeMirror-lines {
237 | cursor: text;
238 | min-height: 1px; /* prevents collapsing before first draw */
239 | }
240 | .CodeMirror pre.CodeMirror-line,
241 | .CodeMirror pre.CodeMirror-line-like {
242 | /* Reset some styles that the rest of the page might have set */
243 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
244 | border-width: 0;
245 | background: transparent;
246 | font-family: inherit;
247 | font-size: inherit;
248 | margin: 0;
249 | white-space: pre;
250 | word-wrap: normal;
251 | line-height: inherit;
252 | color: inherit;
253 | z-index: 2;
254 | position: relative;
255 | overflow: visible;
256 | -webkit-tap-highlight-color: transparent;
257 | -webkit-font-variant-ligatures: contextual;
258 | font-variant-ligatures: contextual;
259 | }
260 | .CodeMirror-wrap pre.CodeMirror-line,
261 | .CodeMirror-wrap pre.CodeMirror-line-like {
262 | word-wrap: break-word;
263 | white-space: pre-wrap;
264 | word-break: normal;
265 | }
266 |
267 | .CodeMirror-linebackground {
268 | position: absolute;
269 | left: 0; right: 0; top: 0; bottom: 0;
270 | z-index: 0;
271 | }
272 |
273 | .CodeMirror-linewidget {
274 | position: relative;
275 | z-index: 2;
276 | padding: 0.1px; /* Force widget margins to stay inside of the container */
277 | }
278 |
279 | .CodeMirror-widget {}
280 |
281 | .CodeMirror-rtl pre { direction: rtl; }
282 |
283 | .CodeMirror-code {
284 | outline: none;
285 | }
286 |
287 | /* Force content-box sizing for the elements where we expect it */
288 | .CodeMirror-scroll,
289 | .CodeMirror-sizer,
290 | .CodeMirror-gutter,
291 | .CodeMirror-gutters,
292 | .CodeMirror-linenumber {
293 | -moz-box-sizing: content-box;
294 | box-sizing: content-box;
295 | }
296 |
297 | .CodeMirror-measure {
298 | position: absolute;
299 | width: 100%;
300 | height: 0;
301 | overflow: hidden;
302 | visibility: hidden;
303 | }
304 |
305 | .CodeMirror-cursor {
306 | position: absolute;
307 | pointer-events: none;
308 | }
309 | .CodeMirror-measure pre { position: static; }
310 |
311 | div.CodeMirror-cursors {
312 | visibility: hidden;
313 | position: relative;
314 | z-index: 3;
315 | }
316 | div.CodeMirror-dragcursors {
317 | visibility: visible;
318 | }
319 |
320 | .CodeMirror-focused div.CodeMirror-cursors {
321 | visibility: visible;
322 | }
323 |
324 | .CodeMirror-selected { background: #d9d9d9; }
325 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
326 | .CodeMirror-crosshair { cursor: crosshair; }
327 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
328 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
329 |
330 | .cm-searching {
331 | background-color: #ffa;
332 | background-color: rgba(255, 255, 0, .4);
333 | }
334 |
335 | /* Used to force a border model for a node */
336 | .cm-force-border { padding-right: .1px; }
337 |
338 | @media print {
339 | /* Hide the cursor when printing */
340 | .CodeMirror div.CodeMirror-cursors {
341 | visibility: hidden;
342 | }
343 | }
344 |
345 | /* See issue #2901 */
346 | .cm-tab-wrap-hack:after { content: ''; }
347 |
348 | /* Help users use markselection to safely style text background */
349 | span.CodeMirror-selectedtext { background: none; }
350 |
--------------------------------------------------------------------------------
/servers/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "noita-ws-example-servers",
3 | "version": "0.0.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@arr/every": {
8 | "version": "1.0.1",
9 | "resolved": "https://registry.npmjs.org/@arr/every/-/every-1.0.1.tgz",
10 | "integrity": "sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg=="
11 | },
12 | "@polka/url": {
13 | "version": "0.5.0",
14 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz",
15 | "integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw=="
16 | },
17 | "ajv": {
18 | "version": "6.10.2",
19 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
20 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
21 | "requires": {
22 | "fast-deep-equal": "^2.0.1",
23 | "fast-json-stable-stringify": "^2.0.0",
24 | "json-schema-traverse": "^0.4.1",
25 | "uri-js": "^4.2.2"
26 | }
27 | },
28 | "asn1": {
29 | "version": "0.2.4",
30 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
31 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
32 | "requires": {
33 | "safer-buffer": "~2.1.0"
34 | }
35 | },
36 | "assert-plus": {
37 | "version": "1.0.0",
38 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
39 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
40 | },
41 | "async-limiter": {
42 | "version": "1.0.1",
43 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
44 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
45 | },
46 | "asynckit": {
47 | "version": "0.4.0",
48 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
49 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
50 | },
51 | "aws-sign2": {
52 | "version": "0.7.0",
53 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
54 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
55 | },
56 | "aws4": {
57 | "version": "1.8.0",
58 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
59 | "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
60 | },
61 | "bcrypt-pbkdf": {
62 | "version": "1.0.2",
63 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
64 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
65 | "requires": {
66 | "tweetnacl": "^0.14.3"
67 | }
68 | },
69 | "caseless": {
70 | "version": "0.12.0",
71 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
72 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
73 | },
74 | "combined-stream": {
75 | "version": "1.0.8",
76 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
77 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
78 | "requires": {
79 | "delayed-stream": "~1.0.0"
80 | }
81 | },
82 | "core-util-is": {
83 | "version": "1.0.2",
84 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
85 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
86 | },
87 | "dashdash": {
88 | "version": "1.14.1",
89 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
90 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
91 | "requires": {
92 | "assert-plus": "^1.0.0"
93 | }
94 | },
95 | "debug": {
96 | "version": "2.6.9",
97 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
98 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
99 | "requires": {
100 | "ms": "2.0.0"
101 | },
102 | "dependencies": {
103 | "ms": {
104 | "version": "2.0.0",
105 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
106 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
107 | }
108 | }
109 | },
110 | "delayed-stream": {
111 | "version": "1.0.0",
112 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
113 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
114 | },
115 | "depd": {
116 | "version": "1.1.2",
117 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
118 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
119 | },
120 | "destroy": {
121 | "version": "1.0.4",
122 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
123 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
124 | },
125 | "dotenv": {
126 | "version": "5.0.1",
127 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz",
128 | "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow=="
129 | },
130 | "ecc-jsbn": {
131 | "version": "0.1.2",
132 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
133 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
134 | "requires": {
135 | "jsbn": "~0.1.0",
136 | "safer-buffer": "^2.1.0"
137 | }
138 | },
139 | "ee-first": {
140 | "version": "1.1.1",
141 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
142 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
143 | },
144 | "encodeurl": {
145 | "version": "1.0.2",
146 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
147 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
148 | },
149 | "escape-html": {
150 | "version": "1.0.3",
151 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
152 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
153 | },
154 | "etag": {
155 | "version": "1.8.1",
156 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
157 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
158 | },
159 | "extend": {
160 | "version": "3.0.2",
161 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
162 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
163 | },
164 | "extsprintf": {
165 | "version": "1.3.0",
166 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
167 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
168 | },
169 | "fast-deep-equal": {
170 | "version": "2.0.1",
171 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
172 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
173 | },
174 | "fast-json-stable-stringify": {
175 | "version": "2.0.0",
176 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
177 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
178 | },
179 | "forever-agent": {
180 | "version": "0.6.1",
181 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
182 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
183 | },
184 | "form-data": {
185 | "version": "2.3.3",
186 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
187 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
188 | "requires": {
189 | "asynckit": "^0.4.0",
190 | "combined-stream": "^1.0.6",
191 | "mime-types": "^2.1.12"
192 | }
193 | },
194 | "fresh": {
195 | "version": "0.5.2",
196 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
197 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
198 | },
199 | "getpass": {
200 | "version": "0.1.7",
201 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
202 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
203 | "requires": {
204 | "assert-plus": "^1.0.0"
205 | }
206 | },
207 | "har-schema": {
208 | "version": "2.0.0",
209 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
210 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
211 | },
212 | "har-validator": {
213 | "version": "5.1.3",
214 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
215 | "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
216 | "requires": {
217 | "ajv": "^6.5.5",
218 | "har-schema": "^2.0.0"
219 | }
220 | },
221 | "http-errors": {
222 | "version": "1.7.3",
223 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
224 | "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
225 | "requires": {
226 | "depd": "~1.1.2",
227 | "inherits": "2.0.4",
228 | "setprototypeof": "1.1.1",
229 | "statuses": ">= 1.5.0 < 2",
230 | "toidentifier": "1.0.0"
231 | }
232 | },
233 | "http-signature": {
234 | "version": "1.2.0",
235 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
236 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
237 | "requires": {
238 | "assert-plus": "^1.0.0",
239 | "jsprim": "^1.2.2",
240 | "sshpk": "^1.7.0"
241 | }
242 | },
243 | "inherits": {
244 | "version": "2.0.4",
245 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
246 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
247 | },
248 | "is-typedarray": {
249 | "version": "1.0.0",
250 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
251 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
252 | },
253 | "is-wsl": {
254 | "version": "2.1.1",
255 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz",
256 | "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog=="
257 | },
258 | "isstream": {
259 | "version": "0.1.2",
260 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
261 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
262 | },
263 | "jsbn": {
264 | "version": "0.1.1",
265 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
266 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
267 | },
268 | "json-schema": {
269 | "version": "0.2.3",
270 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
271 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
272 | },
273 | "json-schema-traverse": {
274 | "version": "0.4.1",
275 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
276 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
277 | },
278 | "json-stringify-safe": {
279 | "version": "5.0.1",
280 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
281 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
282 | },
283 | "jsprim": {
284 | "version": "1.4.1",
285 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
286 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
287 | "requires": {
288 | "assert-plus": "1.0.0",
289 | "extsprintf": "1.3.0",
290 | "json-schema": "0.2.3",
291 | "verror": "1.10.0"
292 | }
293 | },
294 | "matchit": {
295 | "version": "1.0.8",
296 | "resolved": "https://registry.npmjs.org/matchit/-/matchit-1.0.8.tgz",
297 | "integrity": "sha512-CwPPICzozd/ezCzpVwGYG5bMVieaapnA0vvHDQnmQ2u2vZtVLynoPmvFsZjL67hFOvTBhhpqSR0bq3uloDP/Rw==",
298 | "requires": {
299 | "@arr/every": "^1.0.0"
300 | }
301 | },
302 | "mime": {
303 | "version": "1.6.0",
304 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
305 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
306 | },
307 | "mime-db": {
308 | "version": "1.40.0",
309 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
310 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
311 | },
312 | "mime-types": {
313 | "version": "2.1.24",
314 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
315 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
316 | "requires": {
317 | "mime-db": "1.40.0"
318 | }
319 | },
320 | "ms": {
321 | "version": "2.1.1",
322 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
323 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
324 | },
325 | "oauth-sign": {
326 | "version": "0.9.0",
327 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
328 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
329 | },
330 | "on-finished": {
331 | "version": "2.3.0",
332 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
333 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
334 | "requires": {
335 | "ee-first": "1.1.1"
336 | }
337 | },
338 | "open": {
339 | "version": "7.0.0",
340 | "resolved": "https://registry.npmjs.org/open/-/open-7.0.0.tgz",
341 | "integrity": "sha512-K6EKzYqnwQzk+/dzJAQSBORub3xlBTxMz+ntpZpH/LyCa1o6KjXhuN+2npAaI9jaSmU3R1Q8NWf4KUWcyytGsQ==",
342 | "requires": {
343 | "is-wsl": "^2.1.0"
344 | }
345 | },
346 | "parseurl": {
347 | "version": "1.3.3",
348 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
349 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
350 | },
351 | "performance-now": {
352 | "version": "2.1.0",
353 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
354 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
355 | },
356 | "polka": {
357 | "version": "0.5.2",
358 | "resolved": "https://registry.npmjs.org/polka/-/polka-0.5.2.tgz",
359 | "integrity": "sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==",
360 | "requires": {
361 | "@polka/url": "^0.5.0",
362 | "trouter": "^2.0.1"
363 | }
364 | },
365 | "psl": {
366 | "version": "1.4.0",
367 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
368 | "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
369 | },
370 | "punycode": {
371 | "version": "2.1.1",
372 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
373 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
374 | },
375 | "qs": {
376 | "version": "6.5.2",
377 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
378 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
379 | },
380 | "range-parser": {
381 | "version": "1.2.1",
382 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
383 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
384 | },
385 | "request": {
386 | "version": "2.88.0",
387 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
388 | "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
389 | "requires": {
390 | "aws-sign2": "~0.7.0",
391 | "aws4": "^1.8.0",
392 | "caseless": "~0.12.0",
393 | "combined-stream": "~1.0.6",
394 | "extend": "~3.0.2",
395 | "forever-agent": "~0.6.1",
396 | "form-data": "~2.3.2",
397 | "har-validator": "~5.1.0",
398 | "http-signature": "~1.2.0",
399 | "is-typedarray": "~1.0.0",
400 | "isstream": "~0.1.2",
401 | "json-stringify-safe": "~5.0.1",
402 | "mime-types": "~2.1.19",
403 | "oauth-sign": "~0.9.0",
404 | "performance-now": "^2.1.0",
405 | "qs": "~6.5.2",
406 | "safe-buffer": "^5.1.2",
407 | "tough-cookie": "~2.4.3",
408 | "tunnel-agent": "^0.6.0",
409 | "uuid": "^3.3.2"
410 | }
411 | },
412 | "safe-buffer": {
413 | "version": "5.2.0",
414 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
415 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
416 | },
417 | "safer-buffer": {
418 | "version": "2.1.2",
419 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
420 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
421 | },
422 | "send": {
423 | "version": "0.17.1",
424 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
425 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
426 | "requires": {
427 | "debug": "2.6.9",
428 | "depd": "~1.1.2",
429 | "destroy": "~1.0.4",
430 | "encodeurl": "~1.0.2",
431 | "escape-html": "~1.0.3",
432 | "etag": "~1.8.1",
433 | "fresh": "0.5.2",
434 | "http-errors": "~1.7.2",
435 | "mime": "1.6.0",
436 | "ms": "2.1.1",
437 | "on-finished": "~2.3.0",
438 | "range-parser": "~1.2.1",
439 | "statuses": "~1.5.0"
440 | }
441 | },
442 | "serve-static": {
443 | "version": "1.14.1",
444 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
445 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
446 | "requires": {
447 | "encodeurl": "~1.0.2",
448 | "escape-html": "~1.0.3",
449 | "parseurl": "~1.3.3",
450 | "send": "0.17.1"
451 | }
452 | },
453 | "setprototypeof": {
454 | "version": "1.1.1",
455 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
456 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
457 | },
458 | "sshpk": {
459 | "version": "1.16.1",
460 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
461 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
462 | "requires": {
463 | "asn1": "~0.2.3",
464 | "assert-plus": "^1.0.0",
465 | "bcrypt-pbkdf": "^1.0.0",
466 | "dashdash": "^1.12.0",
467 | "ecc-jsbn": "~0.1.1",
468 | "getpass": "^0.1.1",
469 | "jsbn": "~0.1.0",
470 | "safer-buffer": "^2.0.2",
471 | "tweetnacl": "~0.14.0"
472 | }
473 | },
474 | "statuses": {
475 | "version": "1.5.0",
476 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
477 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
478 | },
479 | "tmi.js": {
480 | "version": "1.5.0",
481 | "resolved": "https://registry.npmjs.org/tmi.js/-/tmi.js-1.5.0.tgz",
482 | "integrity": "sha512-JyWKy9dRkZDG1h6PnpE8fJVsTrW82/yANXoP7R3u02vG7PLCvHGRGTWzBwk0ymMJGX9A+YzDx5tXQDsTeJd/5A==",
483 | "requires": {
484 | "request": "2.88.0",
485 | "ws": "6.1.3"
486 | },
487 | "dependencies": {
488 | "ws": {
489 | "version": "6.1.3",
490 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.3.tgz",
491 | "integrity": "sha512-tbSxiT+qJI223AP4iLfQbkbxkwdFcneYinM2+x46Gx2wgvbaOMO36czfdfVUBRTHvzAMRhDd98sA5d/BuWbQdg==",
492 | "requires": {
493 | "async-limiter": "~1.0.0"
494 | }
495 | }
496 | }
497 | },
498 | "toidentifier": {
499 | "version": "1.0.0",
500 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
501 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
502 | },
503 | "tough-cookie": {
504 | "version": "2.4.3",
505 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
506 | "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
507 | "requires": {
508 | "psl": "^1.1.24",
509 | "punycode": "^1.4.1"
510 | },
511 | "dependencies": {
512 | "punycode": {
513 | "version": "1.4.1",
514 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
515 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
516 | }
517 | }
518 | },
519 | "trouter": {
520 | "version": "2.0.1",
521 | "resolved": "https://registry.npmjs.org/trouter/-/trouter-2.0.1.tgz",
522 | "integrity": "sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==",
523 | "requires": {
524 | "matchit": "^1.0.0"
525 | }
526 | },
527 | "tunnel-agent": {
528 | "version": "0.6.0",
529 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
530 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
531 | "requires": {
532 | "safe-buffer": "^5.0.1"
533 | }
534 | },
535 | "tweetnacl": {
536 | "version": "0.14.5",
537 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
538 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
539 | },
540 | "uri-js": {
541 | "version": "4.2.2",
542 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
543 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
544 | "requires": {
545 | "punycode": "^2.1.0"
546 | }
547 | },
548 | "uuid": {
549 | "version": "3.3.3",
550 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
551 | "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
552 | },
553 | "verror": {
554 | "version": "1.10.0",
555 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
556 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
557 | "requires": {
558 | "assert-plus": "^1.0.0",
559 | "core-util-is": "1.0.2",
560 | "extsprintf": "^1.2.0"
561 | }
562 | },
563 | "ws": {
564 | "version": "5.2.2",
565 | "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
566 | "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
567 | "requires": {
568 | "async-limiter": "~1.0.0"
569 | }
570 | },
571 | "xterm": {
572 | "version": "4.2.0-vscode1",
573 | "resolved": "https://registry.npmjs.org/xterm/-/xterm-4.2.0-vscode1.tgz",
574 | "integrity": "sha512-frw5Kprzkko8+G0vp4jOcwTeOpYFbSb2+QShpeQGJ7l3Hg3MTM7kt1G+nLxBPKnkFAmei/JcJTUGJdp/lldAfA=="
575 | },
576 | "xterm-addon-fit": {
577 | "version": "0.3.0",
578 | "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.3.0.tgz",
579 | "integrity": "sha512-kvkiqHVrnMXgyCH9Xn0BOBJ7XaWC/4BgpSWQy3SueqximgW630t/QOankgqkvk11iTOCwWdAY9DTyQBXUMN3lw=="
580 | }
581 | }
582 | }
583 |
--------------------------------------------------------------------------------
/mod_ws_api/data/ws/json.lua:
--------------------------------------------------------------------------------
1 | -- This file is huge and Noita doesn't have real requires, so
2 | -- manually guard against multiple dofiles
3 | if JSON then return end
4 |
5 | -- -*- coding: utf-8 -*-
6 | --
7 | -- Simple JSON encoding and decoding in pure Lua.
8 | --
9 | -- Copyright 2010-2017 Jeffrey Friedl
10 | -- http://regex.info/blog/
11 | -- Latest version: http://regex.info/blog/lua/json
12 | --
13 | -- This code is released under a Creative Commons CC-BY "Attribution" License:
14 | -- http://creativecommons.org/licenses/by/3.0/deed.en_US
15 | --
16 | -- It can be used for any purpose so long as:
17 | -- 1) the copyright notice above is maintained
18 | -- 2) the web-page links above are maintained
19 | -- 3) the 'AUTHOR_NOTE' string below is maintained
20 | --
21 | local VERSION = '20170927.26' -- version history at end of file
22 | local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20170927.26 ]-"
23 |
24 | --
25 | -- The 'AUTHOR_NOTE' variable exists so that information about the source
26 | -- of the package is maintained even in compiled versions. It's also
27 | -- included in OBJDEF below mostly to quiet warnings about unused variables.
28 | --
29 | local OBJDEF = {
30 | VERSION = VERSION,
31 | AUTHOR_NOTE = AUTHOR_NOTE,
32 | }
33 |
34 |
35 | --
36 | -- Simple JSON encoding and decoding in pure Lua.
37 | -- JSON definition: http://www.json.org/
38 | --
39 | --
40 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
41 | --
42 | -- local lua_value = JSON:decode(raw_json_text)
43 | --
44 | -- local raw_json_text = JSON:encode(lua_table_or_value)
45 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
46 | --
47 | --
48 | --
49 | -- DECODING (from a JSON string to a Lua table)
50 | --
51 | --
52 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
53 | --
54 | -- local lua_value = JSON:decode(raw_json_text)
55 | --
56 | -- If the JSON text is for an object or an array, e.g.
57 | -- { "what": "books", "count": 3 }
58 | -- or
59 | -- [ "Larry", "Curly", "Moe" ]
60 | --
61 | -- the result is a Lua table, e.g.
62 | -- { what = "books", count = 3 }
63 | -- or
64 | -- { "Larry", "Curly", "Moe" }
65 | --
66 | --
67 | -- The encode and decode routines accept an optional second argument,
68 | -- "etc", which is not used during encoding or decoding, but upon error
69 | -- is passed along to error handlers. It can be of any type (including nil).
70 | --
71 | --
72 | --
73 | -- ERROR HANDLING DURING DECODE
74 | --
75 | -- With most errors during decoding, this code calls
76 | --
77 | -- JSON:onDecodeError(message, text, location, etc)
78 | --
79 | -- with a message about the error, and if known, the JSON text being
80 | -- parsed and the byte count where the problem was discovered. You can
81 | -- replace the default JSON:onDecodeError() with your own function.
82 | --
83 | -- The default onDecodeError() merely augments the message with data
84 | -- about the text and the location (and, an 'etc' argument had been
85 | -- provided to decode(), its value is tacked onto the message as well),
86 | -- and then calls JSON.assert(), which itself defaults to Lua's built-in
87 | -- assert(), and can also be overridden.
88 | --
89 | -- For example, in an Adobe Lightroom plugin, you might use something like
90 | --
91 | -- function JSON:onDecodeError(message, text, location, etc)
92 | -- LrErrors.throwUserError("Internal Error: invalid JSON data")
93 | -- end
94 | --
95 | -- or even just
96 | --
97 | -- function JSON.assert(message)
98 | -- LrErrors.throwUserError("Internal Error: " .. message)
99 | -- end
100 | --
101 | -- If JSON:decode() is passed a nil, this is called instead:
102 | --
103 | -- JSON:onDecodeOfNilError(message, nil, nil, etc)
104 | --
105 | -- and if JSON:decode() is passed HTML instead of JSON, this is called:
106 | --
107 | -- JSON:onDecodeOfHTMLError(message, text, nil, etc)
108 | --
109 | -- The use of the 'etc' argument allows stronger coordination between
110 | -- decoding and error reporting, especially when you provide your own
111 | -- error-handling routines. Continuing with the the Adobe Lightroom
112 | -- plugin example:
113 | --
114 | -- function JSON:onDecodeError(message, text, location, etc)
115 | -- local note = "Internal Error: invalid JSON data"
116 | -- if type(etc) = 'table' and etc.photo then
117 | -- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName')
118 | -- end
119 | -- LrErrors.throwUserError(note)
120 | -- end
121 | --
122 | -- :
123 | -- :
124 | --
125 | -- for i, photo in ipairs(photosToProcess) do
126 | -- :
127 | -- :
128 | -- local data = JSON:decode(someJsonText, { photo = photo })
129 | -- :
130 | -- :
131 | -- end
132 | --
133 | --
134 | --
135 | -- If the JSON text passed to decode() has trailing garbage (e.g. as with the JSON "[123]xyzzy"),
136 | -- the method
137 | --
138 | -- JSON:onTrailingGarbage(json_text, location, parsed_value, etc)
139 | --
140 | -- is invoked, where:
141 | --
142 | -- 'json_text' is the original JSON text being parsed,
143 | -- 'location' is the count of bytes into 'json_text' where the garbage starts (6 in the example),
144 | -- 'parsed_value' is the Lua result of what was successfully parsed ({123} in the example),
145 | -- 'etc' is as above.
146 | --
147 | -- If JSON:onTrailingGarbage() does not abort, it should return the value decode() should return,
148 | -- or nil + an error message.
149 | --
150 | -- local new_value, error_message = JSON:onTrailingGarbage()
151 | --
152 | -- The default JSON:onTrailingGarbage() simply invokes JSON:onDecodeError("trailing garbage"...),
153 | -- but you can have this package ignore trailing garbage via
154 | --
155 | -- function JSON:onTrailingGarbage(json_text, location, parsed_value, etc)
156 | -- return parsed_value
157 | -- end
158 | --
159 | --
160 | -- DECODING AND STRICT TYPES
161 | --
162 | -- Because both JSON objects and JSON arrays are converted to Lua tables,
163 | -- it's not normally possible to tell which original JSON type a
164 | -- particular Lua table was derived from, or guarantee decode-encode
165 | -- round-trip equivalency.
166 | --
167 | -- However, if you enable strictTypes, e.g.
168 | --
169 | -- JSON = assert(loadfile "JSON.lua")() --load the routines
170 | -- JSON.strictTypes = true
171 | --
172 | -- then the Lua table resulting from the decoding of a JSON object or
173 | -- JSON array is marked via Lua metatable, so that when re-encoded with
174 | -- JSON:encode() it ends up as the appropriate JSON type.
175 | --
176 | -- (This is not the default because other routines may not work well with
177 | -- tables that have a metatable set, for example, Lightroom API calls.)
178 | --
179 | --
180 | -- ENCODING (from a lua table to a JSON string)
181 | --
182 | -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines
183 | --
184 | -- local raw_json_text = JSON:encode(lua_table_or_value)
185 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
186 | -- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false })
187 | --
188 | -- On error during encoding, this code calls:
189 | --
190 | -- JSON:onEncodeError(message, etc)
191 | --
192 | -- which you can override in your local JSON object. Also see "HANDLING UNSUPPORTED VALUE TYPES" below.
193 | --
194 | -- The 'etc' in the error call is the second argument to encode() and encode_pretty(), or nil if it wasn't provided.
195 | --
196 | --
197 | --
198 | --
199 | -- ENCODING OPTIONS
200 | --
201 | -- An optional third argument, a table of options, can be provided to encode().
202 | --
203 | -- encode_options = {
204 | -- -- options for making "pretty" human-readable JSON (see "PRETTY-PRINTING" below)
205 | -- pretty = true, -- turn pretty formatting on
206 | -- indent = " ", -- use this indent for each level of an array/object
207 | -- align_keys = false, -- if true, align the keys in a way that sounds like it should be nice, but is actually ugly
208 | -- array_newline = false, -- if true, array elements become one to a line rather than inline
209 | --
210 | -- -- other output-related options
211 | -- null = "\0", -- see "ENCODING JSON NULL VALUES" below
212 | -- stringsAreUtf8 = false, -- see "HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA" below
213 | -- }
214 | --
215 | -- json_string = JSON:encode(mytable, etc, encode_options)
216 | --
217 | --
218 | --
219 | -- For reference, the defaults are:
220 | --
221 | -- pretty = false
222 | -- null = nil,
223 | -- stringsAreUtf8 = false,
224 | --
225 | --
226 | --
227 | -- PRETTY-PRINTING
228 | --
229 | -- Enabling the 'pretty' encode option helps generate human-readable JSON.
230 | --
231 | -- pretty = JSON:encode(val, etc, {
232 | -- pretty = true,
233 | -- indent = " ",
234 | -- align_keys = false,
235 | -- })
236 | --
237 | -- encode_pretty() is also provided: it's identical to encode() except
238 | -- that encode_pretty() provides a default options table if none given in the call:
239 | --
240 | -- { pretty = true, indent = " ", align_keys = false, array_newline = false }
241 | --
242 | -- For example, if
243 | --
244 | -- JSON:encode(data)
245 | --
246 | -- produces:
247 | --
248 | -- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11}
249 | --
250 | -- then
251 | --
252 | -- JSON:encode_pretty(data)
253 | --
254 | -- produces:
255 | --
256 | -- {
257 | -- "city": "Kyoto",
258 | -- "climate": {
259 | -- "avg_temp": 16,
260 | -- "humidity": "high",
261 | -- "snowfall": "minimal"
262 | -- },
263 | -- "country": "Japan",
264 | -- "wards": 11
265 | -- }
266 | --
267 | -- The following lines all return identical strings:
268 | -- JSON:encode_pretty(data)
269 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = false, array_newline = false})
270 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = " " })
271 | -- JSON:encode (data, nil, { pretty = true, indent = " " })
272 | --
273 | -- An example of setting your own indent string:
274 | --
275 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " })
276 | --
277 | -- produces:
278 | --
279 | -- {
280 | -- | "city": "Kyoto",
281 | -- | "climate": {
282 | -- | | "avg_temp": 16,
283 | -- | | "humidity": "high",
284 | -- | | "snowfall": "minimal"
285 | -- | },
286 | -- | "country": "Japan",
287 | -- | "wards": 11
288 | -- }
289 | --
290 | -- An example of setting align_keys to true:
291 | --
292 | -- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true })
293 | --
294 | -- produces:
295 | --
296 | -- {
297 | -- "city": "Kyoto",
298 | -- "climate": {
299 | -- "avg_temp": 16,
300 | -- "humidity": "high",
301 | -- "snowfall": "minimal"
302 | -- },
303 | -- "country": "Japan",
304 | -- "wards": 11
305 | -- }
306 | --
307 | -- which I must admit is kinda ugly, sorry. This was the default for
308 | -- encode_pretty() prior to version 20141223.14.
309 | --
310 | --
311 | -- HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA
312 | --
313 | -- If the 'stringsAreUtf8' encode option is set to true, consider Lua strings not as a sequence of bytes,
314 | -- but as a sequence of UTF-8 characters.
315 | --
316 | -- Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH
317 | -- separators, if found in a string, are encoded with a JSON escape instead of being dumped as is.
318 | -- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON
319 | -- to also be valid Java.
320 | --
321 | -- AMBIGUOUS SITUATIONS DURING THE ENCODING
322 | --
323 | -- During the encode, if a Lua table being encoded contains both string
324 | -- and numeric keys, it fits neither JSON's idea of an object, nor its
325 | -- idea of an array. To get around this, when any string key exists (or
326 | -- when non-positive numeric keys exist), numeric keys are converted to
327 | -- strings.
328 | --
329 | -- For example,
330 | -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
331 | -- produces the JSON object
332 | -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"}
333 | --
334 | -- To prohibit this conversion and instead make it an error condition, set
335 | -- JSON.noKeyConversion = true
336 | --
337 | --
338 | -- ENCODING JSON NULL VALUES
339 | --
340 | -- Lua tables completely omit keys whose value is nil, so without special handling there's
341 | -- no way to represent JSON object's null value in a Lua table. For example
342 | -- JSON:encode({ username = "admin", password = nil })
343 | --
344 | -- produces:
345 | --
346 | -- {"username":"admin"}
347 | --
348 | -- In order to actually produce
349 | --
350 | -- {"username":"admin", "password":null}
351 | --
352 |
353 | -- one can include a string value for a "null" field in the options table passed to encode()....
354 | -- any Lua table entry with that value becomes null in the JSON output:
355 | --
356 | -- JSON:encode({ username = "admin", password = "xyzzy" }, -- First arg is the Lua table to encode as JSON.
357 | -- nil, -- Second arg is the 'etc' value, ignored here
358 | -- { null = "xyzzy" }) -- Third arg is th options table
359 | --
360 | -- produces:
361 | --
362 | -- {"username":"admin", "password":null}
363 | --
364 | -- Just be sure to use a string that is otherwise unlikely to appear in your data.
365 | -- The string "\0" (a string with one null byte) may well be appropriate for many applications.
366 | --
367 | -- The "null" options also applies to Lua tables that become JSON arrays.
368 | -- JSON:encode({ "one", "two", nil, nil })
369 | --
370 | -- produces
371 | --
372 | -- ["one","two"]
373 | --
374 | -- while
375 | --
376 | -- NullPlaceholder = "\0"
377 | -- encode_options = { null = NullPlaceholder }
378 | -- JSON:encode({ "one", "two", NullPlaceholder, NullPlaceholder}, nil, encode_options)
379 | -- produces
380 | --
381 | -- ["one","two",null,null]
382 | --
383 | --
384 | --
385 | -- HANDLING LARGE AND/OR PRECISE NUMBERS
386 | --
387 | --
388 | -- Without special handling, numbers in JSON can lose precision in Lua.
389 | -- For example:
390 | --
391 | -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }')
392 | --
393 | -- print("small: ", type(T.small), T.small)
394 | -- print("big: ", type(T.big), T.big)
395 | -- print("precise: ", type(T.precise), T.precise)
396 | --
397 | -- produces
398 | --
399 | -- small: number 12345
400 | -- big: number 1.2345678901235e+28
401 | -- precise: number 9876.6789012346
402 | --
403 | -- Precision is lost with both 'big' and 'precise'.
404 | --
405 | -- This package offers ways to try to handle this better (for some definitions of "better")...
406 | --
407 | -- The most precise method is by setting the global:
408 | --
409 | -- JSON.decodeNumbersAsObjects = true
410 | --
411 | -- When this is set, numeric JSON data is encoded into Lua in a form that preserves the exact
412 | -- JSON numeric presentation when re-encoded back out to JSON, or accessed in Lua as a string.
413 | --
414 | -- This is done by encoding the numeric data with a Lua table/metatable that returns
415 | -- the possibly-imprecise numeric form when accessed numerically, but the original precise
416 | -- representation when accessed as a string.
417 | --
418 | -- Consider the example above, with this option turned on:
419 | --
420 | -- JSON.decodeNumbersAsObjects = true
421 | --
422 | -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }')
423 | --
424 | -- print("small: ", type(T.small), T.small)
425 | -- print("big: ", type(T.big), T.big)
426 | -- print("precise: ", type(T.precise), T.precise)
427 | --
428 | -- This now produces:
429 | --
430 | -- small: table 12345
431 | -- big: table 12345678901234567890123456789
432 | -- precise: table 9876.67890123456789012345
433 | --
434 | -- However, within Lua you can still use the values (e.g. T.precise in the example above) in numeric
435 | -- contexts. In such cases you'll get the possibly-imprecise numeric version, but in string contexts
436 | -- and when the data finds its way to this package's encode() function, the original full-precision
437 | -- representation is used.
438 | --
439 | -- You can force access to the string or numeric version via
440 | -- JSON:forceString()
441 | -- JSON:forceNumber()
442 | -- For example,
443 | -- local probably_okay = JSON:forceNumber(T.small) -- 'probably_okay' is a number
444 | --
445 | -- Code the inspects the JSON-turned-Lua data using type() can run into troubles because what used to
446 | -- be a number can now be a table (e.g. as the small/big/precise example above shows). Update these
447 | -- situations to use JSON:isNumber(item), which returns nil if the item is neither a number nor one
448 | -- of these number objects. If it is either, it returns the number itself. For completeness there's
449 | -- also JSON:isString(item).
450 | --
451 | -- If you want to try to avoid the hassles of this "number as an object" kludge for all but really
452 | -- big numbers, you can set JSON.decodeNumbersAsObjects and then also set one or both of
453 | -- JSON:decodeIntegerObjectificationLength
454 | -- JSON:decodeDecimalObjectificationLength
455 | -- They refer to the length of the part of the number before and after a decimal point. If they are
456 | -- set and their part is at least that number of digits, objectification occurs. If both are set,
457 | -- objectification occurs when either length is met.
458 | --
459 | -- -----------------------
460 | --
461 | -- Even without using the JSON.decodeNumbersAsObjects option, you can encode numbers in your Lua
462 | -- table that retain high precision upon encoding to JSON, by using the JSON:asNumber() function:
463 | --
464 | -- T = {
465 | -- imprecise = 123456789123456789.123456789123456789,
466 | -- precise = JSON:asNumber("123456789123456789.123456789123456789")
467 | -- }
468 | --
469 | -- print(JSON:encode_pretty(T))
470 | --
471 | -- This produces:
472 | --
473 | -- {
474 | -- "precise": 123456789123456789.123456789123456789,
475 | -- "imprecise": 1.2345678912346e+17
476 | -- }
477 | --
478 | --
479 | -- -----------------------
480 | --
481 | -- A different way to handle big/precise JSON numbers is to have decode() merely return the exact
482 | -- string representation of the number instead of the number itself. This approach might be useful
483 | -- when the numbers are merely some kind of opaque object identifier and you want to work with them
484 | -- in Lua as strings anyway.
485 | --
486 | -- This approach is enabled by setting
487 | --
488 | -- JSON.decodeIntegerStringificationLength = 10
489 | --
490 | -- The value is the number of digits (of the integer part of the number) at which to stringify numbers.
491 | -- NOTE: this setting is ignored if JSON.decodeNumbersAsObjects is true, as that takes precedence.
492 | --
493 | -- Consider our previous example with this option set to 10:
494 | --
495 | -- JSON.decodeIntegerStringificationLength = 10
496 | --
497 | -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }')
498 | --
499 | -- print("small: ", type(T.small), T.small)
500 | -- print("big: ", type(T.big), T.big)
501 | -- print("precise: ", type(T.precise), T.precise)
502 | --
503 | -- This produces:
504 | --
505 | -- small: number 12345
506 | -- big: string 12345678901234567890123456789
507 | -- precise: number 9876.6789012346
508 | --
509 | -- The long integer of the 'big' field is at least JSON.decodeIntegerStringificationLength digits
510 | -- in length, so it's converted not to a Lua integer but to a Lua string. Using a value of 0 or 1 ensures
511 | -- that all JSON numeric data becomes strings in Lua.
512 | --
513 | -- Note that unlike
514 | -- JSON.decodeNumbersAsObjects = true
515 | -- this stringification is simple and unintelligent: the JSON number simply becomes a Lua string, and that's the end of it.
516 | -- If the string is then converted back to JSON, it's still a string. After running the code above, adding
517 | -- print(JSON:encode(T))
518 | -- produces
519 | -- {"big":"12345678901234567890123456789","precise":9876.6789012346,"small":12345}
520 | -- which is unlikely to be desired.
521 | --
522 | -- There's a comparable option for the length of the decimal part of a number:
523 | --
524 | -- JSON.decodeDecimalStringificationLength
525 | --
526 | -- This can be used alone or in conjunction with
527 | --
528 | -- JSON.decodeIntegerStringificationLength
529 | --
530 | -- to trip stringification on precise numbers with at least JSON.decodeIntegerStringificationLength digits after
531 | -- the decimal point. (Both are ignored if JSON.decodeNumbersAsObjects is true.)
532 | --
533 | -- This example:
534 | --
535 | -- JSON.decodeIntegerStringificationLength = 10
536 | -- JSON.decodeDecimalStringificationLength = 5
537 | --
538 | -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }')
539 | --
540 | -- print("small: ", type(T.small), T.small)
541 | -- print("big: ", type(T.big), T.big)
542 | -- print("precise: ", type(T.precise), T.precise)
543 | --
544 | -- produces:
545 | --
546 | -- small: number 12345
547 | -- big: string 12345678901234567890123456789
548 | -- precise: string 9876.67890123456789012345
549 | --
550 | --
551 | -- HANDLING UNSUPPORTED VALUE TYPES
552 | --
553 | -- Among the encoding errors that might be raised is an attempt to convert a table value that has a type
554 | -- that this package hasn't accounted for: a function, userdata, or a thread. You can handle these types as table
555 | -- values (but not as table keys) if you supply a JSON:unsupportedTypeEncoder() method along the lines of the
556 | -- following example:
557 | --
558 | -- function JSON:unsupportedTypeEncoder(value_of_unsupported_type)
559 | -- if type(value_of_unsupported_type) == 'function' then
560 | -- return "a function value"
561 | -- else
562 | -- return nil
563 | -- end
564 | -- end
565 | --
566 | -- Your unsupportedTypeEncoder() method is actually called with a bunch of arguments:
567 | --
568 | -- self:unsupportedTypeEncoder(value, parents, etc, options, indent, for_key)
569 | --
570 | -- The 'value' is the function, thread, or userdata to be converted to JSON.
571 | --
572 | -- The 'etc' and 'options' arguments are those passed to the original encode(). The other arguments are
573 | -- probably of little interest; see the source code. (Note that 'for_key' is never true, as this function
574 | -- is invoked only on table values; table keys of these types still trigger the onEncodeError method.)
575 | --
576 | -- If your unsupportedTypeEncoder() method returns a string, it's inserted into the JSON as is.
577 | -- If it returns nil plus an error message, that error message is passed through to an onEncodeError invocation.
578 | -- If it returns only nil, processing falls through to a default onEncodeError invocation.
579 | --
580 | -- If you want to handle everything in a simple way:
581 | --
582 | -- function JSON:unsupportedTypeEncoder(value)
583 | -- return tostring(value)
584 | -- end
585 | --
586 | --
587 | -- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT
588 | --
589 | -- assert
590 | -- onDecodeError
591 | -- onDecodeOfNilError
592 | -- onDecodeOfHTMLError
593 | -- onTrailingGarbage
594 | -- onEncodeError
595 | -- unsupportedTypeEncoder
596 | --
597 | -- If you want to create a separate Lua JSON object with its own error handlers,
598 | -- you can reload JSON.lua or use the :new() method.
599 | --
600 | ---------------------------------------------------------------------------
601 |
602 | local default_pretty_indent = " "
603 | local default_pretty_options = { pretty = true, indent = default_pretty_indent, align_keys = false, array_newline = false }
604 |
605 | local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray
606 | local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject
607 |
608 | function OBJDEF:newArray(tbl)
609 | return setmetatable(tbl or {}, isArray)
610 | end
611 |
612 | function OBJDEF:newObject(tbl)
613 | return setmetatable(tbl or {}, isObject)
614 | end
615 |
616 |
617 |
618 |
619 | local function getnum(op)
620 | return type(op) == 'number' and op or op.N
621 | end
622 |
623 | local isNumber = {
624 | __tostring = function(T) return T.S end,
625 | __unm = function(op) return getnum(op) end,
626 |
627 | __concat = function(op1, op2) return tostring(op1) .. tostring(op2) end,
628 | __add = function(op1, op2) return getnum(op1) + getnum(op2) end,
629 | __sub = function(op1, op2) return getnum(op1) - getnum(op2) end,
630 | __mul = function(op1, op2) return getnum(op1) * getnum(op2) end,
631 | __div = function(op1, op2) return getnum(op1) / getnum(op2) end,
632 | __mod = function(op1, op2) return getnum(op1) % getnum(op2) end,
633 | __pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end,
634 | __lt = function(op1, op2) return getnum(op1) < getnum(op2) end,
635 | __eq = function(op1, op2) return getnum(op1) == getnum(op2) end,
636 | __le = function(op1, op2) return getnum(op1) <= getnum(op2) end,
637 | }
638 | isNumber.__index = isNumber
639 |
640 | function OBJDEF:asNumber(item)
641 |
642 | if getmetatable(item) == isNumber then
643 | -- it's already a JSON number object.
644 | return item
645 | elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then
646 | -- it's a number-object table that lost its metatable, so give it one
647 | return setmetatable(item, isNumber)
648 | else
649 | -- the normal situation... given a number or a string representation of a number....
650 | local holder = {
651 | S = tostring(item), -- S is the representation of the number as a string, which remains precise
652 | N = tonumber(item), -- N is the number as a Lua number.
653 | }
654 | return setmetatable(holder, isNumber)
655 | end
656 | end
657 |
658 | --
659 | -- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above,
660 | -- return the string version. This shouldn't be needed often because the 'isNumber' object should autoconvert
661 | -- to a string in most cases, but it's here to allow it to be forced when needed.
662 | --
663 | function OBJDEF:forceString(item)
664 | if type(item) == 'table' and type(item.S) == 'string' then
665 | return item.S
666 | else
667 | return tostring(item)
668 | end
669 | end
670 |
671 | --
672 | -- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above,
673 | -- return the numeric version.
674 | --
675 | function OBJDEF:forceNumber(item)
676 | if type(item) == 'table' and type(item.N) == 'number' then
677 | return item.N
678 | else
679 | return tonumber(item)
680 | end
681 | end
682 |
683 | --
684 | -- If the given item is a number, return it. Otherwise, return nil.
685 | -- This, this can be used both in a conditional and to access the number when you're not sure its form.
686 | --
687 | function OBJDEF:isNumber(item)
688 | if type(item) == 'number' then
689 | return item
690 | elseif type(item) == 'table' and type(item.N) == 'number' then
691 | return item.N
692 | else
693 | return nil
694 | end
695 | end
696 |
697 | function OBJDEF:isString(item)
698 | if type(item) == 'string' then
699 | return item
700 | elseif type(item) == 'table' and type(item.S) == 'string' then
701 | return item.S
702 | else
703 | return nil
704 | end
705 | end
706 |
707 |
708 | local function unicode_codepoint_as_utf8(codepoint)
709 | --
710 | -- codepoint is a number
711 | --
712 | if codepoint <= 127 then
713 | return string.char(codepoint)
714 |
715 | elseif codepoint <= 2047 then
716 | --
717 | -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8
718 | --
719 | local highpart = math.floor(codepoint / 0x40)
720 | local lowpart = codepoint - (0x40 * highpart)
721 | return string.char(0xC0 + highpart,
722 | 0x80 + lowpart)
723 |
724 | elseif codepoint <= 65535 then
725 | --
726 | -- 1110yyyy 10yyyyxx 10xxxxxx
727 | --
728 | local highpart = math.floor(codepoint / 0x1000)
729 | local remainder = codepoint - 0x1000 * highpart
730 | local midpart = math.floor(remainder / 0x40)
731 | local lowpart = remainder - 0x40 * midpart
732 |
733 | highpart = 0xE0 + highpart
734 | midpart = 0x80 + midpart
735 | lowpart = 0x80 + lowpart
736 |
737 | --
738 | -- Check for an invalid character (thanks Andy R. at Adobe).
739 | -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
740 | --
741 | if ( highpart == 0xE0 and midpart < 0xA0 ) or
742 | ( highpart == 0xED and midpart > 0x9F ) or
743 | ( highpart == 0xF0 and midpart < 0x90 ) or
744 | ( highpart == 0xF4 and midpart > 0x8F )
745 | then
746 | return "?"
747 | else
748 | return string.char(highpart,
749 | midpart,
750 | lowpart)
751 | end
752 |
753 | else
754 | --
755 | -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
756 | --
757 | local highpart = math.floor(codepoint / 0x40000)
758 | local remainder = codepoint - 0x40000 * highpart
759 | local midA = math.floor(remainder / 0x1000)
760 | remainder = remainder - 0x1000 * midA
761 | local midB = math.floor(remainder / 0x40)
762 | local lowpart = remainder - 0x40 * midB
763 |
764 | return string.char(0xF0 + highpart,
765 | 0x80 + midA,
766 | 0x80 + midB,
767 | 0x80 + lowpart)
768 | end
769 | end
770 |
771 | function OBJDEF:onDecodeError(message, text, location, etc)
772 | if text then
773 | if location then
774 | message = string.format("%s at byte %d of: %s", message, location, text)
775 | else
776 | message = string.format("%s: %s", message, text)
777 | end
778 | end
779 |
780 | if etc ~= nil then
781 | message = message .. " (" .. OBJDEF:encode(etc) .. ")"
782 | end
783 |
784 | if self.assert then
785 | self.assert(false, message)
786 | else
787 | assert(false, message)
788 | end
789 | end
790 |
791 | function OBJDEF:onTrailingGarbage(json_text, location, parsed_value, etc)
792 | return self:onDecodeError("trailing garbage", json_text, location, etc)
793 | end
794 |
795 | OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError
796 | OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError
797 |
798 | function OBJDEF:onEncodeError(message, etc)
799 | if etc ~= nil then
800 | message = message .. " (" .. OBJDEF:encode(etc) .. ")"
801 | end
802 |
803 | if self.assert then
804 | self.assert(false, message)
805 | else
806 | assert(false, message)
807 | end
808 | end
809 |
810 | local function grok_number(self, text, start, options)
811 | --
812 | -- Grab the integer part
813 | --
814 | local integer_part = text:match('^-?[1-9]%d*', start)
815 | or text:match("^-?0", start)
816 |
817 | if not integer_part then
818 | self:onDecodeError("expected number", text, start, options.etc)
819 | return nil, start -- in case the error method doesn't abort, return something sensible
820 | end
821 |
822 | local i = start + integer_part:len()
823 |
824 | --
825 | -- Grab an optional decimal part
826 | --
827 | local decimal_part = text:match('^%.%d+', i) or ""
828 |
829 | i = i + decimal_part:len()
830 |
831 | --
832 | -- Grab an optional exponential part
833 | --
834 | local exponent_part = text:match('^[eE][-+]?%d+', i) or ""
835 |
836 | i = i + exponent_part:len()
837 |
838 | local full_number_text = integer_part .. decimal_part .. exponent_part
839 |
840 | if options.decodeNumbersAsObjects then
841 |
842 | local objectify = false
843 |
844 | if not options.decodeIntegerObjectificationLength and not options.decodeDecimalObjectificationLength then
845 | -- no options, so objectify
846 | objectify = true
847 |
848 | elseif (options.decodeIntegerObjectificationLength
849 | and
850 | (integer_part:len() >= options.decodeIntegerObjectificationLength or exponent_part:len() > 0))
851 |
852 | or
853 | (options.decodeDecimalObjectificationLength
854 | and
855 | (decimal_part:len() >= options.decodeDecimalObjectificationLength or exponent_part:len() > 0))
856 | then
857 | -- have options and they are triggered, so objectify
858 | objectify = true
859 | end
860 |
861 | if objectify then
862 | return OBJDEF:asNumber(full_number_text), i
863 | end
864 | -- else, fall through to try to return as a straight-up number
865 |
866 | else
867 |
868 | -- Not always decoding numbers as objects, so perhaps encode as strings?
869 |
870 | --
871 | -- If we're told to stringify only under certain conditions, so do.
872 | -- We punt a bit when there's an exponent by just stringifying no matter what.
873 | -- I suppose we should really look to see whether the exponent is actually big enough one
874 | -- way or the other to trip stringification, but I'll be lazy about it until someone asks.
875 | --
876 | if (options.decodeIntegerStringificationLength
877 | and
878 | (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0))
879 |
880 | or
881 |
882 | (options.decodeDecimalStringificationLength
883 | and
884 | (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0))
885 | then
886 | return full_number_text, i -- this returns the exact string representation seen in the original JSON
887 | end
888 |
889 | end
890 |
891 |
892 | local as_number = tonumber(full_number_text)
893 |
894 | if not as_number then
895 | self:onDecodeError("bad number", text, start, options.etc)
896 | return nil, start -- in case the error method doesn't abort, return something sensible
897 | end
898 |
899 | return as_number, i
900 | end
901 |
902 |
903 | local function grok_string(self, text, start, options)
904 |
905 | if text:sub(start,start) ~= '"' then
906 | self:onDecodeError("expected string's opening quote", text, start, options.etc)
907 | return nil, start -- in case the error method doesn't abort, return something sensible
908 | end
909 |
910 | local i = start + 1 -- +1 to bypass the initial quote
911 | local text_len = text:len()
912 | local VALUE = ""
913 | while i <= text_len do
914 | local c = text:sub(i,i)
915 | if c == '"' then
916 | return VALUE, i + 1
917 | end
918 | if c ~= '\\' then
919 | VALUE = VALUE .. c
920 | i = i + 1
921 | elseif text:match('^\\b', i) then
922 | VALUE = VALUE .. "\b"
923 | i = i + 2
924 | elseif text:match('^\\f', i) then
925 | VALUE = VALUE .. "\f"
926 | i = i + 2
927 | elseif text:match('^\\n', i) then
928 | VALUE = VALUE .. "\n"
929 | i = i + 2
930 | elseif text:match('^\\r', i) then
931 | VALUE = VALUE .. "\r"
932 | i = i + 2
933 | elseif text:match('^\\t', i) then
934 | VALUE = VALUE .. "\t"
935 | i = i + 2
936 | else
937 | local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
938 | if hex then
939 | i = i + 6 -- bypass what we just read
940 |
941 | -- We have a Unicode codepoint. It could be standalone, or if in the proper range and
942 | -- followed by another in a specific range, it'll be a two-code surrogate pair.
943 | local codepoint = tonumber(hex, 16)
944 | if codepoint >= 0xD800 and codepoint <= 0xDBFF then
945 | -- it's a hi surrogate... see whether we have a following low
946 | local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
947 | if lo_surrogate then
948 | i = i + 6 -- bypass the low surrogate we just read
949 | codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)
950 | else
951 | -- not a proper low, so we'll just leave the first codepoint as is and spit it out.
952 | end
953 | end
954 | VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)
955 |
956 | else
957 |
958 | -- just pass through what's escaped
959 | VALUE = VALUE .. text:match('^\\(.)', i)
960 | i = i + 2
961 | end
962 | end
963 | end
964 |
965 | self:onDecodeError("unclosed string", text, start, options.etc)
966 | return nil, start -- in case the error method doesn't abort, return something sensible
967 | end
968 |
969 | local function skip_whitespace(text, start)
970 |
971 | local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2
972 | if match_end then
973 | return match_end + 1
974 | else
975 | return start
976 | end
977 | end
978 |
979 | local grok_one -- assigned later
980 |
981 | local function grok_object(self, text, start, options)
982 |
983 | if text:sub(start,start) ~= '{' then
984 | self:onDecodeError("expected '{'", text, start, options.etc)
985 | return nil, start -- in case the error method doesn't abort, return something sensible
986 | end
987 |
988 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'
989 |
990 | local VALUE = self.strictTypes and self:newObject { } or { }
991 |
992 | if text:sub(i,i) == '}' then
993 | return VALUE, i + 1
994 | end
995 | local text_len = text:len()
996 | while i <= text_len do
997 | local key, new_i = grok_string(self, text, i, options)
998 |
999 | i = skip_whitespace(text, new_i)
1000 |
1001 | if text:sub(i, i) ~= ':' then
1002 | self:onDecodeError("expected colon", text, i, options.etc)
1003 | return nil, i -- in case the error method doesn't abort, return something sensible
1004 | end
1005 |
1006 | i = skip_whitespace(text, i + 1)
1007 |
1008 | local new_val, new_i = grok_one(self, text, i, options)
1009 |
1010 | VALUE[key] = new_val
1011 |
1012 | --
1013 | -- Expect now either '}' to end things, or a ',' to allow us to continue.
1014 | --
1015 | i = skip_whitespace(text, new_i)
1016 |
1017 | local c = text:sub(i,i)
1018 |
1019 | if c == '}' then
1020 | return VALUE, i + 1
1021 | end
1022 |
1023 | if text:sub(i, i) ~= ',' then
1024 | self:onDecodeError("expected comma or '}'", text, i, options.etc)
1025 | return nil, i -- in case the error method doesn't abort, return something sensible
1026 | end
1027 |
1028 | i = skip_whitespace(text, i + 1)
1029 | end
1030 |
1031 | self:onDecodeError("unclosed '{'", text, start, options.etc)
1032 | return nil, start -- in case the error method doesn't abort, return something sensible
1033 | end
1034 |
1035 | local function grok_array(self, text, start, options)
1036 | if text:sub(start,start) ~= '[' then
1037 | self:onDecodeError("expected '['", text, start, options.etc)
1038 | return nil, start -- in case the error method doesn't abort, return something sensible
1039 | end
1040 |
1041 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '['
1042 | local VALUE = self.strictTypes and self:newArray { } or { }
1043 | if text:sub(i,i) == ']' then
1044 | return VALUE, i + 1
1045 | end
1046 |
1047 | local VALUE_INDEX = 1
1048 |
1049 | local text_len = text:len()
1050 | while i <= text_len do
1051 | local val, new_i = grok_one(self, text, i, options)
1052 |
1053 | -- can't table.insert(VALUE, val) here because it's a no-op if val is nil
1054 | VALUE[VALUE_INDEX] = val
1055 | VALUE_INDEX = VALUE_INDEX + 1
1056 |
1057 | i = skip_whitespace(text, new_i)
1058 |
1059 | --
1060 | -- Expect now either ']' to end things, or a ',' to allow us to continue.
1061 | --
1062 | local c = text:sub(i,i)
1063 | if c == ']' then
1064 | return VALUE, i + 1
1065 | end
1066 | if text:sub(i, i) ~= ',' then
1067 | self:onDecodeError("expected comma or ']'", text, i, options.etc)
1068 | return nil, i -- in case the error method doesn't abort, return something sensible
1069 | end
1070 | i = skip_whitespace(text, i + 1)
1071 | end
1072 | self:onDecodeError("unclosed '['", text, start, options.etc)
1073 | return nil, i -- in case the error method doesn't abort, return something sensible
1074 | end
1075 |
1076 |
1077 | grok_one = function(self, text, start, options)
1078 | -- Skip any whitespace
1079 | start = skip_whitespace(text, start)
1080 |
1081 | if start > text:len() then
1082 | self:onDecodeError("unexpected end of string", text, nil, options.etc)
1083 | return nil, start -- in case the error method doesn't abort, return something sensible
1084 | end
1085 |
1086 | if text:find('^"', start) then
1087 | return grok_string(self, text, start, options.etc)
1088 |
1089 | elseif text:find('^[-0123456789 ]', start) then
1090 | return grok_number(self, text, start, options)
1091 |
1092 | elseif text:find('^%{', start) then
1093 | return grok_object(self, text, start, options)
1094 |
1095 | elseif text:find('^%[', start) then
1096 | return grok_array(self, text, start, options)
1097 |
1098 | elseif text:find('^true', start) then
1099 | return true, start + 4
1100 |
1101 | elseif text:find('^false', start) then
1102 | return false, start + 5
1103 |
1104 | elseif text:find('^null', start) then
1105 | return options.null, start + 4
1106 |
1107 | else
1108 | self:onDecodeError("can't parse JSON", text, start, options.etc)
1109 | return nil, 1 -- in case the error method doesn't abort, return something sensible
1110 | end
1111 | end
1112 |
1113 | function OBJDEF:decode(text, etc, options)
1114 | --
1115 | -- If the user didn't pass in a table of decode options, make an empty one.
1116 | --
1117 | if type(options) ~= 'table' then
1118 | options = {}
1119 | end
1120 |
1121 | --
1122 | -- If they passed in an 'etc' argument, stuff it into the options.
1123 | -- (If not, any 'etc' field in the options they passed in remains to be used)
1124 | --
1125 | if etc ~= nil then
1126 | options.etc = etc
1127 | end
1128 |
1129 |
1130 | if type(self) ~= 'table' or self.__index ~= OBJDEF then
1131 | local error_message = "JSON:decode must be called in method format"
1132 | OBJDEF:onDecodeError(error_message, nil, nil, options.etc)
1133 | return nil, error_message -- in case the error method doesn't abort, return something sensible
1134 | end
1135 |
1136 | if text == nil then
1137 | local error_message = "nil passed to JSON:decode()"
1138 | self:onDecodeOfNilError(error_message, nil, nil, options.etc)
1139 | return nil, error_message -- in case the error method doesn't abort, return something sensible
1140 |
1141 | elseif type(text) ~= 'string' then
1142 | local error_message = "expected string argument to JSON:decode()"
1143 | self:onDecodeError(string.format("%s, got %s", error_message, type(text)), nil, nil, options.etc)
1144 | return nil, error_message -- in case the error method doesn't abort, return something sensible
1145 | end
1146 |
1147 | if text:match('^%s*$') then
1148 | -- an empty string is nothing, but not an error
1149 | return nil
1150 | end
1151 |
1152 | if text:match('^%s*<') then
1153 | -- Can't be JSON... we'll assume it's HTML
1154 | local error_message = "HTML passed to JSON:decode()"
1155 | self:onDecodeOfHTMLError(error_message, text, nil, options.etc)
1156 | return nil, error_message -- in case the error method doesn't abort, return something sensible
1157 | end
1158 |
1159 | --
1160 | -- Ensure that it's not UTF-32 or UTF-16.
1161 | -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),
1162 | -- but this package can't handle them.
1163 | --
1164 | if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then
1165 | local error_message = "JSON package groks only UTF-8, sorry"
1166 | self:onDecodeError(error_message, text, nil, options.etc)
1167 | return nil, error_message -- in case the error method doesn't abort, return something sensible
1168 | end
1169 |
1170 | --
1171 | -- apply global options
1172 | --
1173 | if options.decodeNumbersAsObjects == nil then
1174 | options.decodeNumbersAsObjects = self.decodeNumbersAsObjects
1175 | end
1176 | if options.decodeIntegerObjectificationLength == nil then
1177 | options.decodeIntegerObjectificationLength = self.decodeIntegerObjectificationLength
1178 | end
1179 | if options.decodeDecimalObjectificationLength == nil then
1180 | options.decodeDecimalObjectificationLength = self.decodeDecimalObjectificationLength
1181 | end
1182 | if options.decodeIntegerStringificationLength == nil then
1183 | options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength
1184 | end
1185 | if options.decodeDecimalStringificationLength == nil then
1186 | options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength
1187 | end
1188 |
1189 |
1190 | --
1191 | -- Finally, go parse it
1192 | --
1193 | local success, value, next_i = pcall(grok_one, self, text, 1, options)
1194 |
1195 | if success then
1196 |
1197 | local error_message = nil
1198 | if next_i ~= #text + 1 then
1199 | -- something's left over after we parsed the first thing.... whitespace is allowed.
1200 | next_i = skip_whitespace(text, next_i)
1201 |
1202 | -- if we have something left over now, it's trailing garbage
1203 | if next_i ~= #text + 1 then
1204 | value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc)
1205 | end
1206 | end
1207 | return value, error_message
1208 |
1209 | else
1210 |
1211 | -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received
1212 | -- the error message here as "value", so pass it along as an assert.
1213 | local error_message = value
1214 | if self.assert then
1215 | self.assert(false, error_message)
1216 | else
1217 | assert(false, error_message)
1218 | end
1219 | -- ...and if we're still here (because the assert didn't throw an error),
1220 | -- return a nil and throw the error message on as a second arg
1221 | return nil, error_message
1222 |
1223 | end
1224 | end
1225 |
1226 | local function backslash_replacement_function(c)
1227 | if c == "\n" then
1228 | return "\\n"
1229 | elseif c == "\r" then
1230 | return "\\r"
1231 | elseif c == "\t" then
1232 | return "\\t"
1233 | elseif c == "\b" then
1234 | return "\\b"
1235 | elseif c == "\f" then
1236 | return "\\f"
1237 | elseif c == '"' then
1238 | return '\\"'
1239 | elseif c == '\\' then
1240 | return '\\\\'
1241 | else
1242 | return string.format("\\u%04x", c:byte())
1243 | end
1244 | end
1245 |
1246 | local chars_to_be_escaped_in_JSON_string
1247 | = '['
1248 | .. '"' -- class sub-pattern to match a double quote
1249 | .. '%\\' -- class sub-pattern to match a backslash
1250 | .. '%z' -- class sub-pattern to match a null
1251 | .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters
1252 | .. ']'
1253 |
1254 |
1255 | local LINE_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2028)
1256 | local PARAGRAPH_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2029)
1257 | local function json_string_literal(value, options)
1258 | local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)
1259 | if options.stringsAreUtf8 then
1260 | --
1261 | -- This feels really ugly to just look into a string for the sequence of bytes that we know to be a particular utf8 character,
1262 | -- but utf8 was designed purposefully to make this kind of thing possible. Still, feels dirty.
1263 | -- I'd rather decode the byte stream into a character stream, but it's not technically needed so
1264 | -- not technically worth it.
1265 | --
1266 | newval = newval:gsub(LINE_SEPARATOR_as_utf8, '\\u2028'):gsub(PARAGRAPH_SEPARATOR_as_utf8,'\\u2029')
1267 | end
1268 | return '"' .. newval .. '"'
1269 | end
1270 |
1271 | local function object_or_array(self, T, etc)
1272 | --
1273 | -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON
1274 | -- object. If there are only numbers, it's a JSON array.
1275 | --
1276 | -- If we'll be converting to a JSON object, we'll want to sort the keys so that the
1277 | -- end result is deterministic.
1278 | --
1279 | local string_keys = { }
1280 | local number_keys = { }
1281 | local number_keys_must_be_strings = false
1282 | local maximum_number_key
1283 |
1284 | for key in pairs(T) do
1285 | if type(key) == 'string' then
1286 | table.insert(string_keys, key)
1287 | elseif type(key) == 'number' then
1288 | table.insert(number_keys, key)
1289 | if key <= 0 or key >= math.huge then
1290 | number_keys_must_be_strings = true
1291 | elseif not maximum_number_key or key > maximum_number_key then
1292 | maximum_number_key = key
1293 | end
1294 | elseif type(key) == 'boolean' then
1295 | table.insert(string_keys, tostring(key))
1296 | else
1297 | self:onEncodeError("can't encode table with a key of type " .. type(key), etc)
1298 | end
1299 | end
1300 |
1301 | if #string_keys == 0 and not number_keys_must_be_strings then
1302 | --
1303 | -- An empty table, or a numeric-only array
1304 | --
1305 | if #number_keys > 0 then
1306 | return nil, maximum_number_key -- an array
1307 | elseif tostring(T) == "JSON array" then
1308 | return nil
1309 | elseif tostring(T) == "JSON object" then
1310 | return { }
1311 | else
1312 | -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects
1313 | return nil
1314 | end
1315 | end
1316 |
1317 | table.sort(string_keys)
1318 |
1319 | local map
1320 | if #number_keys > 0 then
1321 | --
1322 | -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array
1323 | -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.
1324 | --
1325 |
1326 | if self.noKeyConversion then
1327 | self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc)
1328 | end
1329 |
1330 | --
1331 | -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings
1332 | --
1333 | map = { }
1334 | for key, val in pairs(T) do
1335 | map[key] = val
1336 | end
1337 |
1338 | table.sort(number_keys)
1339 |
1340 | --
1341 | -- Throw numeric keys in there as strings
1342 | --
1343 | for _, number_key in ipairs(number_keys) do
1344 | local string_key = tostring(number_key)
1345 | if map[string_key] == nil then
1346 | table.insert(string_keys , string_key)
1347 | map[string_key] = T[number_key]
1348 | else
1349 | self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc)
1350 | end
1351 | end
1352 | end
1353 |
1354 | return string_keys, nil, map
1355 | end
1356 |
1357 | --
1358 | -- Encode
1359 | --
1360 | -- 'options' is nil, or a table with possible keys:
1361 | --
1362 | -- pretty -- If true, return a pretty-printed version.
1363 | --
1364 | -- indent -- A string (usually of spaces) used to indent each nested level.
1365 | --
1366 | -- align_keys -- If true, align all the keys when formatting a table. The result is uglier than one might at first imagine.
1367 | -- Results are undefined if 'align_keys' is true but 'pretty' is not.
1368 | --
1369 | -- array_newline -- If true, array elements are formatted each to their own line. The default is to all fall inline.
1370 | -- Results are undefined if 'array_newline' is true but 'pretty' is not.
1371 | --
1372 | -- null -- If this exists with a string value, table elements with this value are output as JSON null.
1373 | --
1374 | -- stringsAreUtf8 -- If true, consider Lua strings not as a sequence of bytes, but as a sequence of UTF-8 characters.
1375 | -- (Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH
1376 | -- separators, if found in a string, are encoded with a JSON escape instead of as raw UTF-8.
1377 | -- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON
1378 | -- to also be valid Java.)
1379 | --
1380 | --
1381 | local function encode_value(self, value, parents, etc, options, indent, for_key)
1382 |
1383 | --
1384 | -- keys in a JSON object can never be null, so we don't even consider options.null when converting a key value
1385 | --
1386 | if value == nil or (not for_key and options and options.null and value == options.null) then
1387 | return 'null'
1388 |
1389 | elseif type(value) == 'string' then
1390 | return json_string_literal(value, options)
1391 |
1392 | elseif type(value) == 'number' then
1393 | if value ~= value then
1394 | --
1395 | -- NaN (Not a Number).
1396 | -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.
1397 | --
1398 | return "null"
1399 | elseif value >= math.huge then
1400 | --
1401 | -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should
1402 | -- really be a package option. Note: at least with some implementations, positive infinity
1403 | -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is.
1404 | -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">="
1405 | -- case first.
1406 | --
1407 | return "1e+9999"
1408 | elseif value <= -math.huge then
1409 | --
1410 | -- Negative infinity.
1411 | -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.
1412 | --
1413 | return "-1e+9999"
1414 | else
1415 | return tostring(value)
1416 | end
1417 |
1418 | elseif type(value) == 'boolean' then
1419 | return tostring(value)
1420 |
1421 | elseif type(value) ~= 'table' then
1422 |
1423 | if self.unsupportedTypeEncoder then
1424 | local user_value, user_error = self:unsupportedTypeEncoder(value, parents, etc, options, indent, for_key)
1425 | -- If the user's handler returns a string, use that. If it returns nil plus an error message, bail with that.
1426 | -- If only nil returned, fall through to the default error handler.
1427 | if type(user_value) == 'string' then
1428 | return user_value
1429 | elseif user_value ~= nil then
1430 | self:onEncodeError("unsupportedTypeEncoder method returned a " .. type(user_value), etc)
1431 | elseif user_error then
1432 | self:onEncodeError(tostring(user_error), etc)
1433 | end
1434 | end
1435 |
1436 | self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc)
1437 |
1438 | elseif getmetatable(value) == isNumber then
1439 | return tostring(value)
1440 | else
1441 | --
1442 | -- A table to be converted to either a JSON object or array.
1443 | --
1444 | local T = value
1445 |
1446 | if type(options) ~= 'table' then
1447 | options = {}
1448 | end
1449 | if type(indent) ~= 'string' then
1450 | indent = ""
1451 | end
1452 |
1453 | if parents[T] then
1454 | self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
1455 | else
1456 | parents[T] = true
1457 | end
1458 |
1459 | local result_value
1460 |
1461 | local object_keys, maximum_number_key, map = object_or_array(self, T, etc)
1462 | if maximum_number_key then
1463 | --
1464 | -- An array...
1465 | --
1466 | local key_indent
1467 | if options.array_newline then
1468 | key_indent = indent .. tostring(options.indent or "")
1469 | else
1470 | key_indent = indent
1471 | end
1472 |
1473 | local ITEMS = { }
1474 | for i = 1, maximum_number_key do
1475 | table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, key_indent))
1476 | end
1477 |
1478 | if options.array_newline then
1479 | result_value = "[\n" .. key_indent .. table.concat(ITEMS, ",\n" .. key_indent) .. "\n" .. indent .. "]"
1480 | elseif options.pretty then
1481 | result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
1482 | else
1483 | result_value = "[" .. table.concat(ITEMS, ",") .. "]"
1484 | end
1485 |
1486 | elseif object_keys then
1487 | --
1488 | -- An object
1489 | --
1490 | local TT = map or T
1491 |
1492 | if options.pretty then
1493 |
1494 | local KEYS = { }
1495 | local max_key_length = 0
1496 | for _, key in ipairs(object_keys) do
1497 | local encoded = encode_value(self, tostring(key), parents, etc, options, indent, true)
1498 | if options.align_keys then
1499 | max_key_length = math.max(max_key_length, #encoded)
1500 | end
1501 | table.insert(KEYS, encoded)
1502 | end
1503 | local key_indent = indent .. tostring(options.indent or "")
1504 | local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "")
1505 | local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s"
1506 |
1507 | local COMBINED_PARTS = { }
1508 | for i, key in ipairs(object_keys) do
1509 | local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)
1510 | table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
1511 | end
1512 | result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
1513 |
1514 | else
1515 |
1516 | local PARTS = { }
1517 | for _, key in ipairs(object_keys) do
1518 | local encoded_val = encode_value(self, TT[key], parents, etc, options, indent)
1519 | local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent, true)
1520 | table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
1521 | end
1522 | result_value = "{" .. table.concat(PARTS, ",") .. "}"
1523 |
1524 | end
1525 | else
1526 | --
1527 | -- An empty array/object... we'll treat it as an array, though it should really be an option
1528 | --
1529 | result_value = "[]"
1530 | end
1531 |
1532 | parents[T] = false
1533 | return result_value
1534 | end
1535 | end
1536 |
1537 | local function top_level_encode(self, value, etc, options)
1538 | local val = encode_value(self, value, {}, etc, options)
1539 | if val == nil then
1540 | --PRIVATE("may need to revert to the previous public verison if I can't figure out what the guy wanted")
1541 | return val
1542 | else
1543 | return val
1544 | end
1545 | end
1546 |
1547 | function OBJDEF:encode(value, etc, options)
1548 | if type(self) ~= 'table' or self.__index ~= OBJDEF then
1549 | OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
1550 | end
1551 |
1552 | --
1553 | -- If the user didn't pass in a table of decode options, make an empty one.
1554 | --
1555 | if type(options) ~= 'table' then
1556 | options = {}
1557 | end
1558 |
1559 | return top_level_encode(self, value, etc, options)
1560 | end
1561 |
1562 | function OBJDEF:encode_pretty(value, etc, options)
1563 | if type(self) ~= 'table' or self.__index ~= OBJDEF then
1564 | OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc)
1565 | end
1566 |
1567 | --
1568 | -- If the user didn't pass in a table of decode options, use the default pretty ones
1569 | --
1570 | if type(options) ~= 'table' then
1571 | options = default_pretty_options
1572 | end
1573 |
1574 | return top_level_encode(self, value, etc, options)
1575 | end
1576 |
1577 | function OBJDEF.__tostring()
1578 | return "JSON encode/decode package"
1579 | end
1580 |
1581 | OBJDEF.__index = OBJDEF
1582 |
1583 | function OBJDEF:new(args)
1584 | local new = { }
1585 |
1586 | if args then
1587 | for key, val in pairs(args) do
1588 | new[key] = val
1589 | end
1590 | end
1591 |
1592 | return setmetatable(new, OBJDEF)
1593 | end
1594 |
1595 | JSON = OBJDEF:new() -- Noita expects to just dofile everything :(
1596 | --return OBJDEF:new()
1597 |
1598 | --
1599 | -- Version history:
1600 | --
1601 | -- 20170927.26 Use option.null in decoding as well. Thanks to Max Sindwani for the bump, and sorry to Oliver Hitz
1602 | -- whose first mention of it four years ago was completely missed by me.
1603 | --
1604 | -- 20170823.25 Added support for JSON:unsupportedTypeEncoder().
1605 | -- Thanks to Chronos Phaenon Eosphoros (https://github.com/cpeosphoros) for the idea.
1606 | --
1607 | -- 20170819.24 Added support for boolean keys in tables.
1608 | --
1609 | -- 20170416.23 Added the "array_newline" formatting option suggested by yurenchen (http://www.yurenchen.com/)
1610 | --
1611 | -- 20161128.22 Added:
1612 | -- JSON:isString()
1613 | -- JSON:isNumber()
1614 | -- JSON:decodeIntegerObjectificationLength
1615 | -- JSON:decodeDecimalObjectificationLength
1616 | --
1617 | -- 20161109.21 Oops, had a small boo-boo in the previous update.
1618 | --
1619 | -- 20161103.20 Used to silently ignore trailing garbage when decoding. Now fails via JSON:onTrailingGarbage()
1620 | -- http://seriot.ch/parsing_json.php
1621 | --
1622 | -- Built-in error message about "expected comma or ']'" had mistakenly referred to '['
1623 | --
1624 | -- Updated the built-in error reporting to refer to bytes rather than characters.
1625 | --
1626 | -- The decode() method no longer assumes that error handlers abort.
1627 | --
1628 | -- Made the VERSION string a string instead of a number
1629 | --
1630 |
1631 | -- 20160916.19 Fixed the isNumber.__index assignment (thanks to Jack Taylor)
1632 | --
1633 | -- 20160730.18 Added JSON:forceString() and JSON:forceNumber()
1634 | --
1635 | -- 20160728.17 Added concatenation to the metatable for JSON:asNumber()
1636 | --
1637 | -- 20160709.16 Could crash if not passed an options table (thanks jarno heikkinen ).
1638 | --
1639 | -- Made JSON:asNumber() a bit more resilient to being passed the results of itself.
1640 | --
1641 | -- 20160526.15 Added the ability to easily encode null values in JSON, via the new "null" encoding option.
1642 | -- (Thanks to Adam B for bringing up the issue.)
1643 | --
1644 | -- Added some support for very large numbers and precise floats via
1645 | -- JSON.decodeNumbersAsObjects
1646 | -- JSON.decodeIntegerStringificationLength
1647 | -- JSON.decodeDecimalStringificationLength
1648 | --
1649 | -- Added the "stringsAreUtf8" encoding option. (Hat tip to http://lua-users.org/wiki/JsonModules )
1650 | --
1651 | -- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really
1652 | -- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines
1653 | -- more flexible, and changed the default encode_pretty() to be more generally useful.
1654 | --
1655 | -- Added a third 'options' argument to the encode() and encode_pretty() routines, to control
1656 | -- how the encoding takes place.
1657 | --
1658 | -- Updated docs to add assert() call to the loadfile() line, just as good practice so that
1659 | -- if there is a problem loading JSON.lua, the appropriate error message will percolate up.
1660 | --
1661 | -- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string,
1662 | -- so that the source of the package, and its version number, are visible in compiled copies.
1663 | --
1664 | -- 20140911.12 Minor lua cleanup.
1665 | -- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'.
1666 | -- (Thanks to SmugMug's David Parry for these.)
1667 | --
1668 | -- 20140418.11 JSON nulls embedded within an array were being ignored, such that
1669 | -- ["1",null,null,null,null,null,"seven"],
1670 | -- would return
1671 | -- {1,"seven"}
1672 | -- It's now fixed to properly return
1673 | -- {1, nil, nil, nil, nil, nil, "seven"}
1674 | -- Thanks to "haddock" for catching the error.
1675 | --
1676 | -- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up.
1677 | --
1678 | -- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2",
1679 | -- and this caused some problems.
1680 | --
1681 | -- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate,
1682 | -- and had of course diverged (encode_pretty didn't get the fixes that encode got, so
1683 | -- sometimes produced incorrect results; thanks to Mattie for the heads up).
1684 | --
1685 | -- Handle encoding tables with non-positive numeric keys (unlikely, but possible).
1686 | --
1687 | -- If a table has both numeric and string keys, or its numeric keys are inappropriate
1688 | -- (such as being non-positive or infinite), the numeric keys are turned into
1689 | -- string keys appropriate for a JSON object. So, as before,
1690 | -- JSON:encode({ "one", "two", "three" })
1691 | -- produces the array
1692 | -- ["one","two","three"]
1693 | -- but now something with mixed key types like
1694 | -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" }))
1695 | -- instead of throwing an error produces an object:
1696 | -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"}
1697 | --
1698 | -- To maintain the prior throw-an-error semantics, set
1699 | -- JSON.noKeyConversion = true
1700 | --
1701 | -- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry.
1702 | --
1703 | -- 20130120.6 Comment update: added a link to the specific page on my blog where this code can
1704 | -- be found, so that folks who come across the code outside of my blog can find updates
1705 | -- more easily.
1706 | --
1707 | -- 20111207.5 Added support for the 'etc' arguments, for better error reporting.
1708 | --
1709 | -- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.
1710 | --
1711 | -- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:
1712 | --
1713 | -- * When encoding lua for JSON, Sparse numeric arrays are now handled by
1714 | -- spitting out full arrays, such that
1715 | -- JSON:encode({"one", "two", [10] = "ten"})
1716 | -- returns
1717 | -- ["one","two",null,null,null,null,null,null,null,"ten"]
1718 | --
1719 | -- In 20100810.2 and earlier, only up to the first non-null value would have been retained.
1720 | --
1721 | -- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999".
1722 | -- Version 20100810.2 and earlier created invalid JSON in both cases.
1723 | --
1724 | -- * Unicode surrogate pairs are now detected when decoding JSON.
1725 | --
1726 | -- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding
1727 | --
1728 | -- 20100731.1 initial public release
1729 | --
1730 |
--------------------------------------------------------------------------------