Created
September 15, 2014 12:47
-
-
Save Cameron-D/c8cad4e518f8b9bc9760 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
============================================================================================================================================================================= | |
Hits Total Self Child Line | JSON.lua - Times in seconds | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
. . . . 1 | -- -*- coding: utf-8 -*- | |
. . . . 2 | -- | |
. . . . 3 | -- Copyright 2010-2011 Jeffrey Friedl | |
. . . . 4 | -- http://regex.info/blog/ | |
. . . . 5 | -- | |
1 0.00 0.00 0.00 6 | local VERSION = 20130720.01 -- version history at end of file | |
1 0.00 0.00 0.00 7 | local OBJDEF = { VERSION = VERSION } | |
. . . . 8 | | |
. . . . 9 | -- | |
. . . . 10 | -- Simple JSON encoding and decoding in pure Lua. | |
. . . . 11 | -- http://www.json.org/ | |
. . . . 12 | -- | |
. . . . 13 | -- | |
. . . . 14 | -- JSON = (loadfile "JSON.lua")() -- one-time load of the routines | |
. . . . 15 | -- | |
. . . . 16 | -- local lua_value = JSON:decode(raw_json_text) | |
. . . . 17 | -- | |
. . . . 18 | -- local raw_json_text = JSON:encode(lua_table_or_value) | |
. . . . 19 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability | |
. . . . 20 | -- | |
. . . . 21 | -- | |
. . . . 22 | -- DECODING | |
. . . . 23 | -- | |
. . . . 24 | -- JSON = (loadfile "JSON:lua")() -- one-time load of the routines | |
. . . . 25 | -- | |
. . . . 26 | -- local lua_value = JSON:decode(raw_json_text) | |
. . . . 27 | -- | |
. . . . 28 | -- If the JSON text is for an object or an array, e.g. | |
. . . . 29 | -- { "what": "books", "count": 3 } | |
. . . . 30 | -- or | |
. . . . 31 | -- [ "Larry", "Curly", "Moe" ] | |
. . . . 32 | -- | |
. . . . 33 | -- the result is a Lua table, e.g. | |
. . . . 34 | -- { what = "books", count = 3 } | |
. . . . 35 | -- or | |
. . . . 36 | -- { "Larry", "Curly", "Moe" } | |
. . . . 37 | -- | |
. . . . 38 | -- | |
. . . . 39 | -- The encode and decode routines accept an optional second argument, "etc", which is not used | |
. . . . 40 | -- during encoding or decoding, but upon error is passed along to error handlers. It can be of any | |
. . . . 41 | -- type (including nil). | |
. . . . 42 | -- | |
. . . . 43 | -- With most errors during decoding, this code calls | |
. . . . 44 | -- | |
. . . . 45 | -- JSON:onDecodeError(message, text, location, etc) | |
. . . . 46 | -- | |
. . . . 47 | -- with a message about the error, and if known, the JSON text being parsed and the byte count | |
. . . . 48 | -- where the problem was discovered. You can replace the default JSON:onDecodeError() with your | |
. . . . 49 | -- own function. | |
. . . . 50 | -- | |
. . . . 51 | -- The default onDecodeError() merely augments the message with data about the text and the | |
. . . . 52 | -- location if known (and if a second 'etc' argument had been provided to decode(), its value is | |
. . . . 53 | -- tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's | |
. . . . 54 | -- built-in assert(), and can also be overridden. | |
. . . . 55 | -- | |
. . . . 56 | -- For example, in an Adobe Lightroom plugin, you might use something like | |
. . . . 57 | -- | |
. . . . 58 | -- function JSON:onDecodeError(message, text, location, etc) | |
. . . . 59 | -- LrErrors.throwUserError("Internal Error: invalid JSON data") | |
. . . . 60 | -- end | |
. . . . 61 | -- | |
. . . . 62 | -- or even just | |
. . . . 63 | -- | |
. . . . 64 | -- function JSON.assert(message) | |
. . . . 65 | -- LrErrors.throwUserError("Internal Error: " .. message) | |
. . . . 66 | -- end | |
. . . . 67 | -- | |
. . . . 68 | -- If JSON:decode() is passed a nil, this is called instead: | |
. . . . 69 | -- | |
. . . . 70 | -- JSON:onDecodeOfNilError(message, nil, nil, etc) | |
. . . . 71 | -- | |
. . . . 72 | -- and if JSON:decode() is passed HTML instead of JSON, this is called: | |
. . . . 73 | -- | |
. . . . 74 | -- JSON:onDecodeOfHTMLError(message, text, nil, etc) | |
. . . . 75 | -- | |
. . . . 76 | -- The use of the fourth 'etc' argument allows stronger coordination between decoding and error | |
. . . . 77 | -- reporting, especially when you provide your own error-handling routines. Continuing with the | |
. . . . 78 | -- the Adobe Lightroom plugin example: | |
. . . . 79 | -- | |
. . . . 80 | -- function JSON:onDecodeError(message, text, location, etc) | |
. . . . 81 | -- local note = "Internal Error: invalid JSON data" | |
. . . . 82 | -- if type(etc) = 'table' and etc.photo then | |
. . . . 83 | -- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') | |
. . . . 84 | -- end | |
. . . . 85 | -- LrErrors.throwUserError(note) | |
. . . . 86 | -- end | |
. . . . 87 | -- | |
. . . . 88 | -- : | |
. . . . 89 | -- : | |
. . . . 90 | -- | |
. . . . 91 | -- for i, photo in ipairs(photosToProcess) do | |
. . . . 92 | -- : | |
. . . . 93 | -- : | |
. . . . 94 | -- local data = JSON:decode(someJsonText, { photo = photo }) | |
. . . . 95 | -- : | |
. . . . 96 | -- : | |
. . . . 97 | -- end | |
. . . . 98 | -- | |
. . . . 99 | -- | |
. . . . 100 | -- | |
. . . . 101 | -- | |
. . . . 102 | | |
. . . . 103 | -- DECODING AND STRICT TYPES | |
. . . . 104 | -- | |
. . . . 105 | -- Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally | |
. . . . 106 | -- possible to tell which a Lua table came from, or guarantee decode-encode round-trip | |
. . . . 107 | -- equivalency. | |
. . . . 108 | -- | |
. . . . 109 | -- However, if you enable strictTypes, e.g. | |
. . . . 110 | -- | |
. . . . 111 | -- JSON = (loadfile "JSON:lua")() --load the routines | |
. . . . 112 | -- JSON.strictTypes = true | |
. . . . 113 | -- | |
. . . . 114 | -- then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua | |
. . . . 115 | -- metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type. | |
. . . . 116 | -- | |
. . . . 117 | -- (This is not the default because other routines may not work well with tables that have a | |
. . . . 118 | -- metatable set, for example, Lightroom API calls.) | |
. . . . 119 | -- | |
. . . . 120 | -- | |
. . . . 121 | -- ENCODING | |
. . . . 122 | -- | |
. . . . 123 | -- JSON = (loadfile "JSON.lua")() -- one-time load of the routines | |
. . . . 124 | -- | |
. . . . 125 | -- local raw_json_text = JSON:encode(lua_table_or_value) | |
. . . . 126 | -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability | |
. . . . 127 | | |
. . . . 128 | -- On error during encoding, this code calls: | |
. . . . 129 | -- | |
. . . . 130 | -- JSON:onEncodeError(message, etc) | |
. . . . 131 | -- | |
. . . . 132 | -- which you can override in your local JSON object. | |
. . . . 133 | -- | |
. . . . 134 | -- | |
. . . . 135 | -- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT | |
. . . . 136 | -- | |
. . . . 137 | -- assert | |
. . . . 138 | -- onDecodeError | |
. . . . 139 | -- onDecodeOfNilError | |
. . . . 140 | -- onDecodeOfHTMLError | |
. . . . 141 | -- onEncodeError | |
. . . . 142 | -- | |
. . . . 143 | -- If you want to create a separate Lua JSON object with its own error handlers, | |
. . . . 144 | -- you can reload JSON.lua or use the :new() method. | |
. . . . 145 | -- | |
. . . . 146 | --------------------------------------------------------------------------- | |
. . . . 147 | | |
. . . . 148 | | |
1 0.00 0.00 0.00 149 | local author = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json), version " .. tostring(VERSION) .. " ]-" | |
1 0.00 0.00 0.00 150 | local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray | |
1 0.00 0.00 0.00 151 | local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject | |
. . . . 152 | | |
. . . . 153 | | |
1 0.00 0.00 0.00 154 | function OBJDEF:newArray(tbl) | |
. . . . 155 | return setmetatable(tbl or {}, isArray) | |
1 0.00 0.00 0.00 156 | end | |
. . . . 157 | | |
1 0.00 0.00 0.00 158 | function OBJDEF:newObject(tbl) | |
. . . . 159 | return setmetatable(tbl or {}, isObject) | |
1 0.00 0.00 0.00 160 | end | |
. . . . 161 | | |
. . . . 162 | local function unicode_codepoint_as_utf8(codepoint) | |
. . . . 163 | -- | |
. . . . 164 | -- codepoint is a number | |
. . . . 165 | -- | |
. . . . 166 | if codepoint <= 127 then | |
. . . . 167 | return string.char(codepoint) | |
. . . . 168 | | |
. . . . 169 | elseif codepoint <= 2047 then | |
. . . . 170 | -- | |
. . . . 171 | -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 | |
. . . . 172 | -- | |
. . . . 173 | local highpart = math.floor(codepoint / 0x40) | |
. . . . 174 | local lowpart = codepoint - (0x40 * highpart) | |
. . . . 175 | return string.char(0xC0 + highpart, | |
. . . . 176 | 0x80 + lowpart) | |
. . . . 177 | | |
. . . . 178 | elseif codepoint <= 65535 then | |
. . . . 179 | -- | |
. . . . 180 | -- 1110yyyy 10yyyyxx 10xxxxxx | |
. . . . 181 | -- | |
. . . . 182 | local highpart = math.floor(codepoint / 0x1000) | |
. . . . 183 | local remainder = codepoint - 0x1000 * highpart | |
. . . . 184 | local midpart = math.floor(remainder / 0x40) | |
. . . . 185 | local lowpart = remainder - 0x40 * midpart | |
. . . . 186 | | |
. . . . 187 | highpart = 0xE0 + highpart | |
. . . . 188 | midpart = 0x80 + midpart | |
. . . . 189 | lowpart = 0x80 + lowpart | |
. . . . 190 | | |
. . . . 191 | -- | |
. . . . 192 | -- Check for an invalid character (thanks Andy R. at Adobe). | |
. . . . 193 | -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 | |
. . . . 194 | -- | |
. . . . 195 | if ( highpart == 0xE0 and midpart < 0xA0 ) or | |
. . . . 196 | ( highpart == 0xED and midpart > 0x9F ) or | |
. . . . 197 | ( highpart == 0xF0 and midpart < 0x90 ) or | |
. . . . 198 | ( highpart == 0xF4 and midpart > 0x8F ) | |
. . . . 199 | then | |
. . . . 200 | return "?" | |
. . . . 201 | else | |
. . . . 202 | return string.char(highpart, | |
. . . . 203 | midpart, | |
. . . . 204 | lowpart) | |
. . . . 205 | end | |
. . . . 206 | | |
. . . . 207 | else | |
. . . . 208 | -- | |
. . . . 209 | -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx | |
. . . . 210 | -- | |
. . . . 211 | local highpart = math.floor(codepoint / 0x40000) | |
. . . . 212 | local remainder = codepoint - 0x40000 * highpart | |
. . . . 213 | local midA = math.floor(remainder / 0x1000) | |
. . . . 214 | remainder = remainder - 0x1000 * midA | |
. . . . 215 | local midB = math.floor(remainder / 0x40) | |
. . . . 216 | local lowpart = remainder - 0x40 * midB | |
. . . . 217 | | |
. . . . 218 | return string.char(0xF0 + highpart, | |
. . . . 219 | 0x80 + midA, | |
. . . . 220 | 0x80 + midB, | |
. . . . 221 | 0x80 + lowpart) | |
. . . . 222 | end | |
1 0.00 0.00 0.00 223 | end | |
. . . . 224 | | |
1 0.00 0.00 0.00 225 | function OBJDEF:onDecodeError(message, text, location, etc) | |
. . . . 226 | if text then | |
. . . . 227 | if location then | |
. . . . 228 | message = string.format("%s at char %d of: %s", message, location, text) | |
. . . . 229 | else | |
. . . . 230 | message = string.format("%s: %s", message, text) | |
. . . . 231 | end | |
. . . . 232 | end | |
. . . . 233 | if etc ~= nil then | |
. . . . 234 | message = message .. " (" .. OBJDEF:encode(etc) .. ")" | |
. . . . 235 | end | |
. . . . 236 | | |
. . . . 237 | if self.assert then | |
. . . . 238 | self.assert(false, message) | |
. . . . 239 | else | |
. . . . 240 | assert(false, message) | |
. . . . 241 | end | |
1 0.00 0.00 0.00 242 | end | |
. . . . 243 | | |
1 0.00 0.00 0.00 244 | OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError | |
1 0.00 0.00 0.00 245 | OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError | |
. . . . 246 | | |
1 0.00 0.00 0.00 247 | function OBJDEF:onEncodeError(message, etc) | |
. . . . 248 | if etc ~= nil then | |
. . . . 249 | message = message .. " (" .. OBJDEF:encode(etc) .. ")" | |
. . . . 250 | end | |
. . . . 251 | | |
. . . . 252 | if self.assert then | |
. . . . 253 | self.assert(false, message) | |
. . . . 254 | else | |
. . . . 255 | assert(false, message) | |
. . . . 256 | end | |
1 0.00 0.00 0.00 257 | end | |
. . . . 258 | | |
. . . . 259 | local function grok_number(self, text, start, etc) | |
. . . . 260 | -- | |
. . . . 261 | -- Grab the integer part | |
. . . . 262 | -- | |
. . . . 263 | local integer_part = text:match('^-?[1-9]%d*', start) | |
. . . . 264 | or text:match("^-?0", start) | |
. . . . 265 | | |
. . . . 266 | if not integer_part then | |
. . . . 267 | self:onDecodeError("expected number", text, start, etc) | |
. . . . 268 | end | |
. . . . 269 | | |
. . . . 270 | local i = start + integer_part:len() | |
. . . . 271 | | |
. . . . 272 | -- | |
. . . . 273 | -- Grab an optional decimal part | |
. . . . 274 | -- | |
. . . . 275 | local decimal_part = text:match('^%.%d+', i) or "" | |
. . . . 276 | | |
. . . . 277 | i = i + decimal_part:len() | |
. . . . 278 | | |
. . . . 279 | -- | |
. . . . 280 | -- Grab an optional exponential part | |
. . . . 281 | -- | |
. . . . 282 | local exponent_part = text:match('^[eE][-+]?%d+', i) or "" | |
. . . . 283 | | |
. . . . 284 | i = i + exponent_part:len() | |
. . . . 285 | | |
. . . . 286 | local full_number_text = integer_part .. decimal_part .. exponent_part | |
. . . . 287 | local as_number = tonumber(full_number_text) | |
. . . . 288 | | |
. . . . 289 | if not as_number then | |
. . . . 290 | self:onDecodeError("bad number", text, start, etc) | |
. . . . 291 | end | |
. . . . 292 | | |
. . . . 293 | return as_number, i | |
1 0.00 0.00 0.00 294 | end | |
. . . . 295 | | |
. . . . 296 | | |
. . . . 297 | local function grok_string(self, text, start, etc) | |
. . . . 298 | | |
. . . . 299 | if text:sub(start,start) ~= '"' then | |
. . . . 300 | self:onDecodeError("expected string's opening quote", text, start, etc) | |
. . . . 301 | end | |
. . . . 302 | | |
. . . . 303 | local i = start + 1 -- +1 to bypass the initial quote | |
. . . . 304 | local text_len = text:len() | |
. . . . 305 | local VALUE = "" | |
. . . . 306 | while i <= text_len do | |
. . . . 307 | local c = text:sub(i,i) | |
. . . . 308 | if c == '"' then | |
. . . . 309 | return VALUE, i + 1 | |
. . . . 310 | end | |
. . . . 311 | if c ~= '\\' then | |
. . . . 312 | VALUE = VALUE .. c | |
. . . . 313 | i = i + 1 | |
. . . . 314 | elseif text:match('^\\b', i) then | |
. . . . 315 | VALUE = VALUE .. "\b" | |
. . . . 316 | i = i + 2 | |
. . . . 317 | elseif text:match('^\\f', i) then | |
. . . . 318 | VALUE = VALUE .. "\f" | |
. . . . 319 | i = i + 2 | |
. . . . 320 | elseif text:match('^\\n', i) then | |
. . . . 321 | VALUE = VALUE .. "\n" | |
. . . . 322 | i = i + 2 | |
. . . . 323 | elseif text:match('^\\r', i) then | |
. . . . 324 | VALUE = VALUE .. "\r" | |
. . . . 325 | i = i + 2 | |
. . . . 326 | elseif text:match('^\\t', i) then | |
. . . . 327 | VALUE = VALUE .. "\t" | |
. . . . 328 | i = i + 2 | |
. . . . 329 | else | |
. . . . 330 | local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) | |
. . . . 331 | if hex then | |
. . . . 332 | i = i + 6 -- bypass what we just read | |
. . . . 333 | | |
. . . . 334 | -- We have a Unicode codepoint. It could be standalone, or if in the proper range and | |
. . . . 335 | -- followed by another in a specific range, it'll be a two-code surrogate pair. | |
. . . . 336 | local codepoint = tonumber(hex, 16) | |
. . . . 337 | if codepoint >= 0xD800 and codepoint <= 0xDBFF then | |
. . . . 338 | -- it's a hi surrogate... see whether we have a following low | |
. . . . 339 | local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) | |
. . . . 340 | if lo_surrogate then | |
. . . . 341 | i = i + 6 -- bypass the low surrogate we just read | |
. . . . 342 | codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) | |
. . . . 343 | else | |
. . . . 344 | -- not a proper low, so we'll just leave the first codepoint as is and spit it out. | |
. . . . 345 | end | |
. . . . 346 | end | |
. . . . 347 | VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) | |
. . . . 348 | | |
. . . . 349 | else | |
. . . . 350 | | |
. . . . 351 | -- just pass through what's escaped | |
. . . . 352 | VALUE = VALUE .. text:match('^\\(.)', i) | |
. . . . 353 | i = i + 2 | |
. . . . 354 | end | |
. . . . 355 | end | |
. . . . 356 | end | |
. . . . 357 | | |
. . . . 358 | self:onDecodeError("unclosed string", text, start, etc) | |
1 0.00 0.00 0.00 359 | end | |
. . . . 360 | | |
. . . . 361 | local function skip_whitespace(text, start) | |
. . . . 362 | | |
. . . . 363 | local match_start, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 | |
. . . . 364 | if match_end then | |
. . . . 365 | return match_end + 1 | |
. . . . 366 | else | |
. . . . 367 | return start | |
. . . . 368 | end | |
1 0.00 0.00 0.00 369 | end | |
. . . . 370 | | |
1 0.00 0.00 0.00 371 | local grok_one -- assigned later | |
. . . . 372 | | |
. . . . 373 | local function grok_object(self, text, start, etc) | |
. . . . 374 | if not text:sub(start,start) == '{' then | |
. . . . 375 | self:onDecodeError("expected '{'", text, start, etc) | |
. . . . 376 | end | |
. . . . 377 | | |
. . . . 378 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' | |
. . . . 379 | | |
. . . . 380 | local VALUE = self.strictTypes and self:newObject { } or { } | |
. . . . 381 | | |
. . . . 382 | if text:sub(i,i) == '}' then | |
. . . . 383 | return VALUE, i + 1 | |
. . . . 384 | end | |
. . . . 385 | local text_len = text:len() | |
. . . . 386 | while i <= text_len do | |
. . . . 387 | local key, new_i = grok_string(self, text, i, etc) | |
. . . . 388 | | |
. . . . 389 | i = skip_whitespace(text, new_i) | |
. . . . 390 | | |
. . . . 391 | if text:sub(i, i) ~= ':' then | |
. . . . 392 | self:onDecodeError("expected colon", text, i, etc) | |
. . . . 393 | end | |
. . . . 394 | | |
. . . . 395 | i = skip_whitespace(text, i + 1) | |
. . . . 396 | | |
. . . . 397 | local val, new_i = grok_one(self, text, i) | |
. . . . 398 | | |
. . . . 399 | VALUE[key] = val | |
. . . . 400 | | |
. . . . 401 | -- | |
. . . . 402 | -- Expect now either '}' to end things, or a ',' to allow us to continue. | |
. . . . 403 | -- | |
. . . . 404 | i = skip_whitespace(text, new_i) | |
. . . . 405 | | |
. . . . 406 | local c = text:sub(i,i) | |
. . . . 407 | | |
. . . . 408 | if c == '}' then | |
. . . . 409 | return VALUE, i + 1 | |
. . . . 410 | end | |
. . . . 411 | | |
. . . . 412 | if text:sub(i, i) ~= ',' then | |
. . . . 413 | self:onDecodeError("expected comma or '}'", text, i, etc) | |
. . . . 414 | end | |
. . . . 415 | | |
. . . . 416 | i = skip_whitespace(text, i + 1) | |
. . . . 417 | end | |
. . . . 418 | | |
. . . . 419 | self:onDecodeError("unclosed '{'", text, start, etc) | |
1 0.00 0.00 0.00 420 | end | |
. . . . 421 | | |
. . . . 422 | local function grok_array(self, text, start, etc) | |
. . . . 423 | if not text:sub(start,start) == '[' then | |
. . . . 424 | self:onDecodeError("expected '['", text, start, etc) | |
. . . . 425 | end | |
. . . . 426 | | |
. . . . 427 | local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' | |
. . . . 428 | local VALUE = self.strictTypes and self:newArray { } or { } | |
. . . . 429 | if text:sub(i,i) == ']' then | |
. . . . 430 | return VALUE, i + 1 | |
. . . . 431 | end | |
. . . . 432 | | |
. . . . 433 | local text_len = text:len() | |
. . . . 434 | while i <= text_len do | |
. . . . 435 | local val, new_i = grok_one(self, text, i) | |
. . . . 436 | | |
. . . . 437 | table.insert(VALUE, val) | |
. . . . 438 | | |
. . . . 439 | i = skip_whitespace(text, new_i) | |
. . . . 440 | | |
. . . . 441 | -- | |
. . . . 442 | -- Expect now either ']' to end things, or a ',' to allow us to continue. | |
. . . . 443 | -- | |
. . . . 444 | local c = text:sub(i,i) | |
. . . . 445 | if c == ']' then | |
. . . . 446 | return VALUE, i + 1 | |
. . . . 447 | end | |
. . . . 448 | if text:sub(i, i) ~= ',' then | |
. . . . 449 | self:onDecodeError("expected comma or '['", text, i, etc) | |
. . . . 450 | end | |
. . . . 451 | i = skip_whitespace(text, i + 1) | |
. . . . 452 | end | |
. . . . 453 | self:onDecodeError("unclosed '['", text, start, etc) | |
1 0.00 0.00 0.00 454 | end | |
. . . . 455 | | |
. . . . 456 | | |
. . . . 457 | grok_one = function(self, text, start, etc) | |
. . . . 458 | -- Skip any whitespace | |
. . . . 459 | start = skip_whitespace(text, start) | |
. . . . 460 | | |
. . . . 461 | if start > text:len() then | |
. . . . 462 | self:onDecodeError("unexpected end of string", text, nil, etc) | |
. . . . 463 | end | |
. . . . 464 | | |
. . . . 465 | if text:find('^"', start) then | |
. . . . 466 | return grok_string(self, text, start, etc) | |
. . . . 467 | | |
. . . . 468 | elseif text:find('^[-0123456789 ]', start) then | |
. . . . 469 | return grok_number(self, text, start, etc) | |
. . . . 470 | | |
. . . . 471 | elseif text:find('^%{', start) then | |
. . . . 472 | return grok_object(self, text, start, etc) | |
. . . . 473 | | |
. . . . 474 | elseif text:find('^%[', start) then | |
. . . . 475 | return grok_array(self, text, start, etc) | |
. . . . 476 | | |
. . . . 477 | elseif text:find('^true', start) then | |
. . . . 478 | return true, start + 4 | |
. . . . 479 | | |
. . . . 480 | elseif text:find('^false', start) then | |
. . . . 481 | return false, start + 5 | |
. . . . 482 | | |
. . . . 483 | elseif text:find('^null', start) then | |
. . . . 484 | return nil, start + 4 | |
. . . . 485 | | |
. . . . 486 | else | |
. . . . 487 | self:onDecodeError("can't parse JSON", text, start, etc) | |
. . . . 488 | end | |
1 0.00 0.00 0.00 489 | end | |
. . . . 490 | | |
1 0.00 0.00 0.00 491 | function OBJDEF:decode(text, etc) | |
. . . . 492 | if type(self) ~= 'table' or self.__index ~= OBJDEF then | |
. . . . 493 | OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc) | |
. . . . 494 | end | |
. . . . 495 | | |
. . . . 496 | if text == nil then | |
. . . . 497 | self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc) | |
. . . . 498 | elseif type(text) ~= 'string' then | |
. . . . 499 | self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc) | |
. . . . 500 | end | |
. . . . 501 | | |
. . . . 502 | if text:match('^%s*$') then | |
. . . . 503 | return nil | |
. . . . 504 | end | |
. . . . 505 | | |
. . . . 506 | if text:match('^%s*<') then | |
. . . . 507 | -- Can't be JSON... we'll assume it's HTML | |
. . . . 508 | self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc) | |
. . . . 509 | end | |
. . . . 510 | | |
. . . . 511 | -- | |
. . . . 512 | -- Ensure that it's not UTF-32 or UTF-16. | |
. . . . 513 | -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), | |
. . . . 514 | -- but this package can't handle them. | |
. . . . 515 | -- | |
. . . . 516 | if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then | |
. . . . 517 | self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc) | |
. . . . 518 | end | |
. . . . 519 | | |
. . . . 520 | local success, value = pcall(grok_one, self, text, 1, etc) | |
. . . . 521 | if success then | |
. . . . 522 | return value | |
. . . . 523 | else | |
. . . . 524 | -- should never get here... JSON parse errors should have been caught earlier | |
. . . . 525 | assert(false, value) | |
. . . . 526 | return nil | |
. . . . 527 | end | |
1 0.00 0.00 0.00 528 | end | |
. . . . 529 | | |
. . . . 530 | local function backslash_replacement_function(c) | |
. . . . 531 | if c == "\n" then | |
. . . . 532 | return "\\n" | |
. . . . 533 | elseif c == "\r" then | |
. . . . 534 | return "\\r" | |
. . . . 535 | elseif c == "\t" then | |
. . . . 536 | return "\\t" | |
. . . . 537 | elseif c == "\b" then | |
. . . . 538 | return "\\b" | |
. . . . 539 | elseif c == "\f" then | |
. . . . 540 | return "\\f" | |
. . . . 541 | elseif c == '"' then | |
. . . . 542 | return '\\"' | |
. . . . 543 | elseif c == '\\' then | |
. . . . 544 | return '\\\\' | |
. . . . 545 | else | |
. . . . 546 | return string.format("\\u%04x", c:byte()) | |
. . . . 547 | end | |
1 0.00 0.00 0.00 548 | end | |
. . . . 549 | | |
. . . . 550 | local chars_to_be_escaped_in_JSON_string | |
. . . . 551 | = '[' | |
1 0.00 0.00 0.00 552 | .. '"' -- class sub-pattern to match a double quote | |
1 0.00 0.00 0.00 553 | .. '%\\' -- class sub-pattern to match a backslash | |
1 0.00 0.00 0.00 554 | .. '%z' -- class sub-pattern to match a null | |
1 0.00 0.00 0.00 555 | .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters | |
1 0.00 0.00 0.00 556 | .. ']' | |
. . . . 557 | | |
. . . . 558 | local function json_string_literal(value) | |
. . . . 559 | local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) | |
. . . . 560 | return '"' .. newval .. '"' | |
1 0.00 0.00 0.00 561 | end | |
. . . . 562 | | |
. . . . 563 | local function object_or_array(self, T, etc) | |
. . . . 564 | -- | |
. . . . 565 | -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON | |
. . . . 566 | -- object. If there are only numbers, it's a JSON array. | |
. . . . 567 | -- | |
. . . . 568 | -- If we'll be converting to a JSON object, we'll want to sort the keys so that the | |
. . . . 569 | -- end result is deterministic. | |
. . . . 570 | -- | |
. . . . 571 | local string_keys = { } | |
. . . . 572 | local seen_number_key = false | |
. . . . 573 | local maximum_number_key | |
. . . . 574 | | |
. . . . 575 | for key in pairs(T) do | |
. . . . 576 | if type(key) == 'number' then | |
. . . . 577 | seen_number_key = true | |
. . . . 578 | if not maximum_number_key or maximum_number_key < key then | |
. . . . 579 | maximum_number_key = key | |
. . . . 580 | end | |
. . . . 581 | elseif type(key) == 'string' then | |
. . . . 582 | table.insert(string_keys, key) | |
. . . . 583 | else | |
. . . . 584 | self:onEncodeError("can't encode table with a key of type " .. type(key), etc) | |
. . . . 585 | end | |
. . . . 586 | end | |
. . . . 587 | | |
. . . . 588 | if seen_number_key and #string_keys > 0 then | |
. . . . 589 | -- | |
. . . . 590 | -- Mixed key types... don't know what to do, so bail | |
. . . . 591 | -- | |
. . . . 592 | self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) | |
. . . . 593 | | |
. . . . 594 | elseif #string_keys == 0 then | |
. . . . 595 | -- | |
. . . . 596 | -- An array | |
. . . . 597 | -- | |
. . . . 598 | if seen_number_key then | |
. . . . 599 | return nil, maximum_number_key -- an array | |
. . . . 600 | else | |
. . . . 601 | -- | |
. . . . 602 | -- An empty table... | |
. . . . 603 | -- | |
. . . . 604 | if tostring(T) == "JSON array" then | |
. . . . 605 | return nil | |
. . . . 606 | elseif tostring(T) == "JSON object" then | |
. . . . 607 | return { } | |
. . . . 608 | else | |
. . . . 609 | -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects | |
. . . . 610 | return nil | |
. . . . 611 | end | |
. . . . 612 | end | |
. . . . 613 | else | |
. . . . 614 | -- | |
. . . . 615 | -- An object, so return a list of keys | |
. . . . 616 | -- | |
. . . . 617 | table.sort(string_keys) | |
. . . . 618 | return string_keys | |
. . . . 619 | end | |
1 0.00 0.00 0.00 620 | end | |
. . . . 621 | | |
. . . . 622 | -- | |
. . . . 623 | -- Encode | |
. . . . 624 | -- | |
1 0.00 0.00 0.00 625 | local encode_value -- must predeclare because it calls itself | |
. . . . 626 | function encode_value(self, value, parents, etc) | |
. . . . 627 | | |
. . . . 628 | | |
. . . . 629 | if value == nil then | |
. . . . 630 | return 'null' | |
. . . . 631 | end | |
. . . . 632 | | |
. . . . 633 | if type(value) == 'string' then | |
. . . . 634 | return json_string_literal(value) | |
. . . . 635 | elseif type(value) == 'number' then | |
. . . . 636 | if value ~= value then | |
. . . . 637 | -- | |
. . . . 638 | -- NaN (Not a Number). | |
. . . . 639 | -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. | |
. . . . 640 | -- | |
. . . . 641 | return "null" | |
. . . . 642 | elseif value >= math.huge then | |
. . . . 643 | -- | |
. . . . 644 | -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should | |
. . . . 645 | -- really be a package option. Note: at least with some implementations, positive infinity | |
. . . . 646 | -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. | |
. . . . 647 | -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" | |
. . . . 648 | -- case first. | |
. . . . 649 | -- | |
. . . . 650 | return "1e+9999" | |
. . . . 651 | elseif value <= -math.huge then | |
. . . . 652 | -- | |
. . . . 653 | -- Negative infinity. | |
. . . . 654 | -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. | |
. . . . 655 | -- | |
. . . . 656 | return "-1e+9999" | |
. . . . 657 | else | |
. . . . 658 | return tostring(value) | |
. . . . 659 | end | |
. . . . 660 | elseif type(value) == 'boolean' then | |
. . . . 661 | return tostring(value) | |
. . . . 662 | | |
. . . . 663 | elseif type(value) ~= 'table' then | |
. . . . 664 | self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) | |
. . . . 665 | | |
. . . . 666 | else | |
. . . . 667 | -- | |
. . . . 668 | -- A table to be converted to either a JSON object or array. | |
. . . . 669 | -- | |
. . . . 670 | local T = value | |
. . . . 671 | | |
. . . . 672 | if parents[T] then | |
. . . . 673 | self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) | |
. . . . 674 | else | |
. . . . 675 | parents[T] = true | |
. . . . 676 | end | |
. . . . 677 | | |
. . . . 678 | local result_value | |
. . . . 679 | | |
. . . . 680 | local object_keys, maximum_number_key = object_or_array(self, T, etc) | |
. . . . 681 | if maximum_number_key then | |
. . . . 682 | -- | |
. . . . 683 | -- An array... | |
. . . . 684 | -- | |
. . . . 685 | local ITEMS = { } | |
. . . . 686 | for i = 1, maximum_number_key do | |
. . . . 687 | table.insert(ITEMS, encode_value(self, T[i], parents, etc)) | |
. . . . 688 | end | |
. . . . 689 | | |
. . . . 690 | result_value = "[" .. table.concat(ITEMS, ",") .. "]" | |
. . . . 691 | elseif object_keys then | |
. . . . 692 | -- | |
. . . . 693 | -- An object | |
. . . . 694 | -- | |
. . . . 695 | | |
. . . . 696 | -- | |
. . . . 697 | -- We'll always sort the keys, so that comparisons can be made on | |
. . . . 698 | -- the results, etc. The actual order is not particularly | |
. . . . 699 | -- important (e.g. it doesn't matter what character set we sort | |
. . . . 700 | -- as); it's only important that it be deterministic... the same | |
. . . . 701 | -- every time. | |
. . . . 702 | -- | |
. . . . 703 | local PARTS = { } | |
. . . . 704 | for _, key in ipairs(object_keys) do | |
. . . . 705 | local encoded_key = encode_value(self, tostring(key), parents, etc) | |
. . . . 706 | local encoded_val = encode_value(self, T[key], parents, etc) | |
. . . . 707 | table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) | |
. . . . 708 | end | |
. . . . 709 | result_value = "{" .. table.concat(PARTS, ",") .. "}" | |
. . . . 710 | else | |
. . . . 711 | -- | |
. . . . 712 | -- An empty array/object... we'll treat it as an array, though it should really be an option | |
. . . . 713 | -- | |
. . . . 714 | result_value = "[]" | |
. . . . 715 | end | |
. . . . 716 | | |
. . . . 717 | parents[T] = false | |
. . . . 718 | return result_value | |
. . . . 719 | end | |
1 0.00 0.00 0.00 720 | end | |
. . . . 721 | | |
1 0.00 0.00 0.00 722 | local encode_pretty_value -- must predeclare because it calls itself | |
. . . . 723 | function encode_pretty_value(self, value, parents, indent, etc) | |
. . . . 724 | | |
. . . . 725 | if type(value) == 'string' then | |
. . . . 726 | return json_string_literal(value) | |
. . . . 727 | | |
. . . . 728 | elseif type(value) == 'number' then | |
. . . . 729 | return tostring(value) | |
. . . . 730 | | |
. . . . 731 | elseif type(value) == 'boolean' then | |
. . . . 732 | return tostring(value) | |
. . . . 733 | | |
. . . . 734 | elseif type(value) == 'nil' then | |
. . . . 735 | return 'null' | |
. . . . 736 | | |
. . . . 737 | elseif type(value) ~= 'table' then | |
. . . . 738 | self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) | |
. . . . 739 | | |
. . . . 740 | else | |
. . . . 741 | -- | |
. . . . 742 | -- A table to be converted to either a JSON object or array. | |
. . . . 743 | -- | |
. . . . 744 | local T = value | |
. . . . 745 | | |
. . . . 746 | if parents[T] then | |
. . . . 747 | self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) | |
. . . . 748 | end | |
. . . . 749 | parents[T] = true | |
. . . . 750 | | |
. . . . 751 | local result_value | |
. . . . 752 | | |
. . . . 753 | local object_keys = object_or_array(self, T, etc) | |
. . . . 754 | if not object_keys then | |
. . . . 755 | -- | |
. . . . 756 | -- An array... | |
. . . . 757 | -- | |
. . . . 758 | local ITEMS = { } | |
. . . . 759 | for i = 1, #T do | |
. . . . 760 | table.insert(ITEMS, encode_pretty_value(self, T[i], parents, indent, etc)) | |
. . . . 761 | end | |
. . . . 762 | | |
. . . . 763 | result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" | |
. . . . 764 | | |
. . . . 765 | else | |
. . . . 766 | | |
. . . . 767 | -- | |
. . . . 768 | -- An object -- can keys be numbers? | |
. . . . 769 | -- | |
. . . . 770 | | |
. . . . 771 | local KEYS = { } | |
. . . . 772 | local max_key_length = 0 | |
. . . . 773 | for _, key in ipairs(object_keys) do | |
. . . . 774 | local encoded = encode_pretty_value(self, tostring(key), parents, "", etc) | |
. . . . 775 | max_key_length = math.max(max_key_length, #encoded) | |
. . . . 776 | table.insert(KEYS, encoded) | |
. . . . 777 | end | |
. . . . 778 | local key_indent = indent .. " " | |
. . . . 779 | local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4) | |
. . . . 780 | local FORMAT = "%s%" .. tostring(max_key_length) .. "s: %s" | |
. . . . 781 | | |
. . . . 782 | local COMBINED_PARTS = { } | |
. . . . 783 | for i, key in ipairs(object_keys) do | |
. . . . 784 | local encoded_val = encode_pretty_value(self, T[key], parents, subtable_indent, etc) | |
. . . . 785 | table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) | |
. . . . 786 | end | |
. . . . 787 | result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" | |
. . . . 788 | end | |
. . . . 789 | | |
. . . . 790 | parents[T] = false | |
. . . . 791 | return result_value | |
. . . . 792 | end | |
1 0.00 0.00 0.00 793 | end | |
. . . . 794 | | |
1 0.00 0.00 0.00 795 | function OBJDEF:encode(value, etc) | |
. . . . 796 | if type(self) ~= 'table' or self.__index ~= OBJDEF then | |
. . . . 797 | OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) | |
. . . . 798 | end | |
. . . . 799 | | |
. . . . 800 | local parents = {} | |
. . . . 801 | return encode_value(self, value, parents, etc) | |
1 0.00 0.00 0.00 802 | end | |
. . . . 803 | | |
1 0.00 0.00 0.00 804 | function OBJDEF:encode_pretty(value, etc) | |
. . . . 805 | local parents = {} | |
. . . . 806 | local subtable_indent = "" | |
. . . . 807 | return encode_pretty_value(self, value, parents, subtable_indent, etc) | |
1 0.00 0.00 0.00 808 | end | |
. . . . 809 | | |
1 0.00 0.00 0.00 810 | function OBJDEF.__tostring() | |
. . . . 811 | return "JSON encode/decode package" | |
1 0.00 0.00 0.00 812 | end | |
. . . . 813 | | |
1 0.00 0.00 0.00 814 | OBJDEF.__index = OBJDEF | |
. . . . 815 | | |
1 0.00 0.00 0.00 816 | -- Function totals | |
1 0.00 0.00 0.00 816 | function OBJDEF:new(args) | |
1 0.00 0.00 0.00 817 | local new = { } | |
. . . . 818 | | |
1 0.00 0.00 0.00 819 | if args then | |
. . . . 820 | for key, val in pairs(args) do | |
. . . . 821 | new[key] = val | |
. . . . 822 | end | |
. . . . 823 | end | |
. . . . 824 | | |
1 0.00 0.00 0.00 825 | return setmetatable(new, OBJDEF) | |
1 0.00 0.00 0.00 826 | end | |
. . . . 827 | | |
1 0.00 0.00 0.00 828 | return OBJDEF:new() | |
. . . . 829 | | |
. . . . 830 | -- | |
. . . . 831 | -- Version history: | |
. . . . 832 | -- | |
. . . . 833 | -- 20111207.5 Added support for the 'etc' arguments, for better error reporting. | |
. . . . 834 | -- | |
. . . . 835 | -- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. | |
. . . . 836 | -- | |
. . . . 837 | -- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: | |
. . . . 838 | -- | |
. . . . 839 | -- * When encoding lua for JSON, Sparse numeric arrays are now handled by | |
. . . . 840 | -- spitting out full arrays, such that | |
. . . . 841 | -- JSON:encode({"one", "two", [10] = "ten"}) | |
. . . . 842 | -- returns | |
. . . . 843 | -- ["one","two",null,null,null,null,null,null,null,"ten"] | |
. . . . 844 | -- | |
. . . . 845 | -- In 20100810.2 and earlier, only up to the first non-null value would have been retained. | |
. . . . 846 | -- | |
. . . . 847 | -- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". | |
. . . . 848 | -- Version 20100810.2 and earlier created invalid JSON in both cases. | |
. . . . 849 | -- | |
. . . . 850 | -- * Unicode surrogate pairs are now detected when decoding JSON. | |
. . . . 851 | -- | |
. . . . 852 | -- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding | |
. . . . 853 | -- | |
. . . . 854 | -- 20100731.1 initial public release | |
. . . . 855 | -- | |
============================================================================================================================================================================= | |
Hits Total Self Child Line | failure_report.lua - Times in seconds | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
. . . . 1 | -- From https://github.com/lua-shellscript/lua-shellscript/blob/master/src/sh/commands.lua | |
. . . . 2 | local function escape(...) | |
. . . . 3 | local command = type(...) == 'table' and ... or { ... } | |
. . . . 4 | | |
. . . . 5 | for i, s in ipairs(command) do | |
. . . . 6 | s = (tostring(s) or ''):gsub('"', '\\"') | |
. . . . 7 | if s:find '[^A-Za-z0-9_."/-]' then | |
. . . . 8 | s = '"' .. s .. '"' | |
. . . . 9 | elseif s == '' then | |
. . . . 10 | s = '""' | |
. . . . 11 | end | |
. . . . 12 | command[i] = s | |
. . . . 13 | end | |
. . . . 14 | | |
. . . . 15 | return table.concat(command, ' ') | |
1 0.00 0.00 0.00 16 | end | |
. . . . 17 | | |
1 0.00 0.00 0.00 18 | local failure_report_url = 'http://quitpic.at.ninjawedding.org/fail' | |
. . . . 19 | | |
1 0.00 0.00 0.00 20 | function log_failure(status_code, url, downloader, item_type, item_value) | |
. . . . 21 | local template = 'curl -s -X POST %s -F downloader=%s -F response_code=%s -F url=%s -F item_name=%s:%s' | |
. . . . 22 | local command = template:format(failure_report_url, | |
. . . . 23 | escape(downloader), | |
. . . . 24 | escape(status_code), | |
. . . . 25 | escape(url), | |
. . . . 26 | escape(item_type), | |
. . . . 27 | escape(item_value)) | |
. . . . 28 | | |
. . . . 29 | os.execute(command) | |
2 0.00 0.00 0.00 30 | end | |
============================================================================================================================================================================= | |
Hits Total Self Child Line | table_show.lua - Times in seconds | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
. . . . 1 | --[[ | |
. . . . 2 | Author: Julio Manuel Fernandez-Diaz | |
. . . . 3 | Date: January 12, 2007 | |
. . . . 4 | (For Lua 5.1) | |
. . . . 5 | | |
. . . . 6 | Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() | |
. . . . 7 | | |
. . . . 8 | Formats tables with cycles recursively to any depth. | |
. . . . 9 | The output is returned as a string. | |
. . . . 10 | References to other tables are shown as values. | |
. . . . 11 | Self references are indicated. | |
. . . . 12 | | |
. . . . 13 | The string returned is "Lua code", which can be procesed | |
. . . . 14 | (in the case in which indent is composed by spaces or "--"). | |
. . . . 15 | Userdata and function keys and values are shown as strings, | |
. . . . 16 | which logically are exactly not equivalent to the original code. | |
. . . . 17 | | |
. . . . 18 | This routine can serve for pretty formating tables with | |
. . . . 19 | proper indentations, apart from printing them: | |
. . . . 20 | | |
. . . . 21 | print(table.show(t, "t")) -- a typical use | |
. . . . 22 | | |
. . . . 23 | Heavily based on "Saving tables with cycles", PIL2, p. 113. | |
. . . . 24 | | |
. . . . 25 | Arguments: | |
. . . . 26 | t is the table. | |
. . . . 27 | name is the name of the table (optional) | |
. . . . 28 | indent is a first indentation (optional). | |
. . . . 29 | --]] | |
2 0.00 0.00 0.00 30 | function table.show(t, name, indent) | |
. . . . 31 | local cart -- a container | |
. . . . 32 | local autoref -- for self references | |
. . . . 33 | | |
. . . . 34 | --[[ counts the number of elements in a table | |
. . . . 35 | local function tablecount(t) | |
. . . . 36 | local n = 0 | |
. . . . 37 | for _, _ in pairs(t) do n = n+1 end | |
. . . . 38 | return n | |
. . . . 39 | end | |
. . . . 40 | ]] | |
. . . . 41 | -- (RiciLake) returns true if the table is empty | |
. . . . 42 | local function isemptytable(t) return next(t) == nil end | |
. . . . 43 | | |
. . . . 44 | local function basicSerialize (o) | |
. . . . 45 | local so = tostring(o) | |
. . . . 46 | if type(o) == "function" then | |
. . . . 47 | local info = debug.getinfo(o, "S") | |
. . . . 48 | -- info.name is nil because o is not a calling level | |
. . . . 49 | if info.what == "C" then | |
. . . . 50 | return string.format("%q", so .. ", C function") | |
. . . . 51 | else | |
. . . . 52 | -- the information is defined through lines | |
. . . . 53 | return string.format("%q", so .. ", defined in (" .. | |
. . . . 54 | info.linedefined .. "-" .. info.lastlinedefined .. | |
. . . . 55 | ")" .. info.source) | |
. . . . 56 | end | |
. . . . 57 | elseif type(o) == "number" or type(o) == "boolean" then | |
. . . . 58 | return so | |
. . . . 59 | else | |
. . . . 60 | return string.format("%q", so) | |
. . . . 61 | end | |
. . . . 62 | end | |
. . . . 63 | | |
. . . . 64 | local function addtocart (value, name, indent, saved, field) | |
. . . . 65 | indent = indent or "" | |
. . . . 66 | saved = saved or {} | |
. . . . 67 | field = field or name | |
. . . . 68 | | |
. . . . 69 | cart = cart .. indent .. field | |
. . . . 70 | | |
. . . . 71 | if type(value) ~= "table" then | |
. . . . 72 | cart = cart .. " = " .. basicSerialize(value) .. ";\n" | |
. . . . 73 | else | |
. . . . 74 | if saved[value] then | |
. . . . 75 | cart = cart .. " = {}; -- " .. saved[value] | |
. . . . 76 | .. " (self reference)\n" | |
. . . . 77 | autoref = autoref .. name .. " = " .. saved[value] .. ";\n" | |
. . . . 78 | else | |
. . . . 79 | saved[value] = name | |
. . . . 80 | --if tablecount(value) == 0 then | |
. . . . 81 | if isemptytable(value) then | |
. . . . 82 | cart = cart .. " = {};\n" | |
. . . . 83 | else | |
. . . . 84 | cart = cart .. " = {\n" | |
. . . . 85 | for k, v in pairs(value) do | |
. . . . 86 | k = basicSerialize(k) | |
. . . . 87 | local fname = string.format("%s[%s]", name, k) | |
. . . . 88 | field = string.format("[%s]", k) | |
. . . . 89 | -- three spaces between levels | |
. . . . 90 | addtocart(v, fname, indent .. " ", saved, field) | |
. . . . 91 | end | |
. . . . 92 | cart = cart .. indent .. "};\n" | |
. . . . 93 | end | |
. . . . 94 | end | |
. . . . 95 | end | |
. . . . 96 | end | |
. . . . 97 | | |
. . . . 98 | name = name or "__unnamed__" | |
. . . . 99 | if type(t) ~= "table" then | |
. . . . 100 | return name .. " = " .. basicSerialize(t) | |
. . . . 101 | end | |
. . . . 102 | cart, autoref = "", "" | |
. . . . 103 | addtocart(t, name, indent) | |
. . . . 104 | return cart .. autoref | |
2 0.00 0.00 0.00 105 | end | |
============================================================================================================================================================================= | |
Hits Total Self Child Line | twitpic.lua - Times in seconds | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
. . . . 1 | local luatrace = require("luatrace") | |
. . . . 2 | luatrace.tron{trace_file_name="mytrace.txt"} | |
1 0.00 0.00 0.00 3 | local url_count = 0 | |
1 0.00 0.00 0.00 4 | local tries = 0 | |
1 0.00 0.00 0.00 5 | local item_type = os.getenv('item_type') | |
1 0.03 0.00 0.03 6 | local item_value = os.getenv('item_value') | |
1 0.00 0.00 0.00 7 | dofile("urlcode.lua") | |
1 0.01 0.01 0.00 8 | dofile("table_show.lua") | |
1 0.01 0.01 0.00 9 | dofile("failure_report.lua") | |
1 0.01 0.01 0.00 10 | JSON = (loadfile "JSON.lua")() | |
. . . . 11 | | |
. . . . 12 | load_json_file = function(file) | |
. . . . 13 | if file then | |
. . . . 14 | local f = io.open(file) | |
. . . . 15 | local data = f:read("*all") | |
. . . . 16 | f:close() | |
. . . . 17 | return JSON:decode(data) | |
. . . . 18 | else | |
. . . . 19 | return nil | |
. . . . 20 | end | |
1 0.00 0.00 0.00 21 | end | |
. . . . 22 | | |
63 0.05 0.05 0.00 23 | -- Function totals | |
. . . . 23 | read_file = function(file) | |
63 0.00 0.00 0.00 24 | if file then | |
63 0.00 0.00 0.00 25 | local f = assert(io.open(file)) | |
63 0.04 0.04 0.00 26 | local data = f:read("*all") | |
63 0.00 0.00 0.00 27 | f:close() | |
63 0.00 0.00 0.00 28 | return data | |
. . . . 29 | else | |
. . . . 30 | return "" | |
. . . . 31 | end | |
1 0.00 0.00 0.00 32 | end | |
. . . . 33 | | |
1 0.00 0.00 0.00 34 | local downloaded = {} | |
. . . . 35 | | |
. . . . 36 | local admit_failure = function(status_code, url) | |
. . . . 37 | io.stdout:write("Giving up on "..url.."\n") | |
. . . . 38 | io.stdout:flush() | |
. . . . 39 | log_failure(status_code, url, os.getenv('downloader'), item_type, item_value) | |
1 0.00 0.00 0.00 40 | end | |
. . . . 41 | | |
392 0.21 0.21 0.00 42 | -- Function totals | |
1 0.00 0.00 0.00 42 | wget.callbacks.get_urls = function(file, url, is_css, iri) | |
392 0.12 0.12 0.00 43 | local urls = {} | |
. . . . 44 | | |
392 0.00 0.00 0.00 45 | if item_type == "image" then | |
. . . . 46 | | |
392 0.00 0.00 0.00 47 | local twitpicurl = "twitpic%.com/"..item_value | |
. . . . 48 | | |
. . . . 49 | | |
392 0.01 0.01 0.00 50 | if string.match(url, "twitpic%.com/"..item_value.."[0-9a-z]") then | |
63 0.05 0.00 0.05 51 | html = read_file(file) | |
. . . . 52 | | |
. . . . 53 | -- for commentid in string.gmatch(html, '<div class="comment clear" data%-id="([0-9]+)">') do | |
. . . . 54 | -- for commentspage in string.gmatch(url, "twitpic%.com/"..item_value.."[0-9a-z]") do | |
. . . . 55 | -- local media_id = string.match(commentspage, "twitpic%.com/([0-9a-z]+)") | |
. . . . 56 | -- table.insert(urls, { url=("http://twitpic.com/comments/show.json?media_id="..media_id.."&last_seen="..commentid) }) | |
. . . . 57 | -- end | |
. . . . 58 | -- end | |
. . . . 59 | | |
63 0.04 0.04 0.00 60 | for videourl in string.gmatch(html, '<meta name="twitter:player:stream" value="(http[^"]+)"') do | |
. . . . 61 | table.insert(urls, { url=videourl }) | |
. . . . 62 | end | |
. . . . 63 | | |
63 0.02 0.02 0.00 64 | for videosource in string.gmatch(html, '<source src="(http[^"]+)"') do | |
. . . . 65 | table.insert(urls, { url=videosource }) | |
. . . . 66 | end | |
. . . . 67 | | |
124 0.03 0.03 0.00 68 | for imageurl in string.gmatch(html, '<meta name="twitter:image" value="(http[^"]+)"') do | |
61 0.00 0.00 0.00 69 | table.insert(urls, { url=imageurl }) | |
. . . . 70 | end | |
63 0.00 0.00 0.00 71 | end | |
. . . . 72 | | |
. . . . 73 | elseif item_type == "tag" then | |
. . . . 74 | | |
. . . . 75 | if string.match(url, item_value) then | |
. . . . 76 | html = read_file(file) | |
. . . . 77 | | |
. . . . 78 | if string.match(html, '<div class="user%-photo%-content right">') then | |
. . . . 79 | for baseurl in string.gmatch(url, "(http://twitpic%.com/tag/[0-9a-zA-Z]+)") do | |
. . . . 80 | for nextpage in string.gmatch(html, '<div class="right">[^<]+<a href="(%?[^"]+)">[^<]+</a>[^<]+</div>') do | |
. . . . 81 | table.insert(urls, { url=(baseurl.."/"..nextpage) }) | |
. . . . 82 | table.insert(urls, { url=(baseurl..nextpage) }) | |
. . . . 83 | end | |
. . . . 84 | | |
. . . . 85 | for prevpage in string.gmatch(html, '<div class="left">[^<]+<a href="(%?[^"]+)">[^<]+</a>[^<]+</div>') do | |
. . . . 86 | table.insert(urls, { url=(baseurl.."/"..prevpage) }) | |
. . . . 87 | table.insert(urls, { url=(baseurl..prevpage) }) | |
. . . . 88 | end | |
. . . . 89 | end | |
. . . . 90 | else | |
. . . . 91 | if string.match(url, "http://twitpic%.com/tag/[0-9a-zA-Z]+[/]?%?page=[0-9]+") then | |
. . . . 92 | local page = string.match(url, "http://twitpic%.com/tag/[0-9a-zA-Z]+[/]?%?page=([0-9]+)") | |
. . . . 93 | local tagid = string.match(url, "http://twitpic%.com/tag/([0-9a-zA-Z]+)[/]?%?page=[0-9]+") | |
. . . . 94 | local prevpage = page - 1 | |
. . . . 95 | local nextpage = page + 2 | |
. . . . 96 | local prevurlslash = "http://twitpic.com/tag/"..tagid.."/?page="..prevpage | |
. . . . 97 | local prevurl = "http://twitpic.com/tag/"..tagid.."?page="..prevpage | |
. . . . 98 | local nexturlslash = "http://twitpic.com/tag/"..tagid.."/?page="..nextpage | |
. . . . 99 | local nexturl = "http://twitpic.com/tag/"..tagid.."?page="..nextpage | |
. . . . 100 | downloaded[prevurlslash] = true | |
. . . . 101 | downloaded[prevurl] = true | |
. . . . 102 | downloaded[nexturlslash] = true | |
. . . . 103 | downloaded[nexturl] = true | |
. . . . 104 | end | |
. . . . 105 | end | |
. . . . 106 | end | |
. . . . 107 | elseif item_type == "user" then | |
. . . . 108 | if string.match(url, "twitpic%.com/events/[0-9a-zA-Z]+") then | |
. . . . 109 | html = read_file(file) | |
. . . . 110 | | |
. . . . 111 | for eventurl in string.gmatch(html, '<a href="(http[s]?://[^/]+/e/[^"]+)">') do | |
. . . . 112 | table.insert(urls, { url=eventurl }) | |
. . . . 113 | end | |
. . . . 114 | end | |
. . . . 115 | | |
. . . . 116 | for eventjson in string.gmatch(url, "/e/([0-9a-zA-Z]+)") do | |
. . . . 117 | local eventjsonurl = "http://api.twitpic.com/2/event/show.json?id="..eventjson | |
. . . . 118 | table.insert(urls, { url=eventjsonurl }) | |
. . . . 119 | end | |
. . . . 120 | | |
. . . . 121 | if string.match(url, "twitpic%.com/places/[0-9a-zA-Z]+") then | |
. . . . 122 | html = read_file(file) | |
. . . . 123 | for placeurl in string.gmatch(html, '<a href="(http[s]?://[^/]+/place/[^/]+/[^"]+)">') do | |
. . . . 124 | table.insert(urls, { url=placeurl }) | |
. . . . 125 | end | |
. . . . 126 | end | |
. . . . 127 | | |
. . . . 128 | for placejson in string.gmatch(url, "/place/[^/]+/([0-9a-zA-Z]+)") do | |
. . . . 129 | local placejsonurl = "http://api.twitpic.com/2/place/show.json?id="..placejson | |
. . . . 130 | local placeurl = "http://twitpic.com/place/"..placejson | |
. . . . 131 | table.insert(urls, { url=placejsonurl }) | |
. . . . 132 | table.insert(urls, { url=placeurl }) | |
. . . . 133 | end | |
. . . . 134 | | |
. . . . 135 | end | |
. . . . 136 | | |
392 0.00 0.00 0.00 137 | return urls | |
1 0.00 0.00 0.00 138 | end | |
. . . . 139 | | |
3052 0.36 0.36 0.00 140 | -- Function totals | |
1 0.00 0.00 0.00 140 | wget.callbacks.download_child_p = function(urlpos, parent, depth, start_url_parsed, iri, verdict, reason) | |
3052 0.18 0.18 0.00 141 | local url = urlpos["url"]["url"] | |
3052 0.01 0.01 0.00 142 | local ishtml = urlpos["link_expect_html"] | |
3052 0.01 0.01 0.00 143 | local parenturl = parent["url"] | |
3052 0.01 0.01 0.00 144 | local wgetreason = reason | |
. . . . 145 | | |
3052 0.01 0.01 0.00 146 | if downloaded[url] == true then | |
847 0.00 0.00 0.00 147 | return false | |
. . . . 148 | end | |
. . . . 149 | | |
. . . . 150 | -- Chfoo - Can I use "local html = nil" in "wget.callbacks.download_child_p"? | |
2205 0.01 0.01 0.00 151 | local html = nil | |
. . . . 152 | | |
2205 0.01 0.01 0.00 153 | if item_type == "image" then | |
2205 0.02 0.02 0.00 154 | if string.match(url, "/%%5C%%22") or | |
2204 0.02 0.02 0.00 155 | string.match(url, '/[^"]+"') then | |
671 0.00 0.00 0.00 156 | return false | |
1533 0.01 0.01 0.00 157 | elseif string.match(url, "/tag/") then | |
. . . . 158 | return false | |
1533 0.02 0.02 0.00 159 | elseif string.match(url, "cloudfront%.net") or | |
1367 0.01 0.01 0.00 160 | string.match(url, "twimg%.com") or | |
1332 0.01 0.01 0.00 161 | string.match(url, "amazonaws%.com") then | |
201 0.00 0.00 0.00 162 | return verdict | |
1332 0.01 0.01 0.00 163 | elseif string.match(url, "advertise%.twitpic%.com") then | |
61 0.00 0.00 0.00 164 | return false | |
1271 0.01 0.01 0.00 165 | elseif not string.match(url, "twitpic%.com") then | |
583 0.00 0.00 0.00 166 | if ishtml ~= 1 then | |
491 0.00 0.00 0.00 167 | return verdict | |
. . . . 168 | end | |
688 0.01 0.01 0.00 169 | elseif not string.match(url, item_value) then | |
626 0.00 0.00 0.00 170 | if ishtml == 1 then | |
592 0.00 0.00 0.00 171 | return false | |
. . . . 172 | else | |
34 0.00 0.00 0.00 173 | return verdict | |
. . . . 174 | end | |
62 0.00 0.00 0.00 175 | elseif string.match(url, item_value) then | |
62 0.00 0.00 0.00 176 | return verdict | |
. . . . 177 | else | |
. . . . 178 | return false | |
. . . . 179 | end | |
. . . . 180 | elseif item_type == "user" then | |
. . . . 181 | if string.match(url, "/%%5C%%22") or | |
. . . . 182 | string.match(url, '/[^"]+"') then | |
. . . . 183 | return false | |
. . . . 184 | elseif string.match(url, "/e/") then | |
. . . . 185 | return true | |
. . . . 186 | elseif string.match(url, "/place/") then | |
. . . . 187 | return true | |
. . . . 188 | elseif string.match(url, "%.json") then | |
. . . . 189 | return true | |
. . . . 190 | elseif string.match(url, "cloudfront%.net") or | |
. . . . 191 | string.match(url, "twimg%.com") or | |
. . . . 192 | string.match(url, "api%.twitpic%.com") or | |
. . . . 193 | string.match(url, "amazonaws%.com") then | |
. . . . 194 | return verdict | |
. . . . 195 | elseif string.match(url, "advertise%.twitpic%.com") then | |
. . . . 196 | return false | |
. . . . 197 | elseif not string.match(url, "twitpic%.com") then | |
. . . . 198 | if ishtml ~= 1 then | |
. . . . 199 | return verdict | |
. . . . 200 | end | |
. . . . 201 | elseif not string.match(url, item_value) then | |
. . . . 202 | if ishtml == 1 then | |
. . . . 203 | return false | |
. . . . 204 | else | |
. . . . 205 | return verdict | |
. . . . 206 | end | |
. . . . 207 | elseif string.match(url, "twitpic%.com/tag/[0-9a-zA-Z]+[/]?&page") then | |
. . . . 208 | if tagpage == 1 then | |
. . . . 209 | return verdict | |
. . . . 210 | else | |
. . . . 211 | return false | |
. . . . 212 | end | |
. . . . 213 | elseif string.match(url, item_value) then | |
. . . . 214 | return verdict | |
. . . . 215 | else | |
. . . . 216 | return false | |
. . . . 217 | end | |
. . . . 218 | elseif item_type == "tag" then | |
. . . . 219 | if string.match(url, "/%%5C%%22") or | |
. . . . 220 | string.match(url, '/[^"]+"') then | |
. . . . 221 | return false | |
. . . . 222 | elseif string.match(url, "cloudfront%.net") or | |
. . . . 223 | string.match(url, "twimg%.com") or | |
. . . . 224 | string.match(url, "amazonaws%.com") then | |
. . . . 225 | return verdict | |
. . . . 226 | elseif string.match(url, "advertise%.twitpic%.com") then | |
. . . . 227 | return false | |
. . . . 228 | elseif not string.match(url, "twitpic%.com") then | |
. . . . 229 | if ishtml ~= 1 then | |
. . . . 230 | return verdict | |
. . . . 231 | end | |
. . . . 232 | elseif not string.match(url, item_value) then | |
. . . . 233 | if ishtml == 1 then | |
. . . . 234 | return false | |
. . . . 235 | else | |
. . . . 236 | return verdict | |
. . . . 237 | end | |
. . . . 238 | elseif string.match(url, item_value) then | |
. . . . 239 | return verdict | |
. . . . 240 | else | |
. . . . 241 | return false | |
. . . . 242 | end | |
. . . . 243 | else | |
. . . . 244 | return verdict | |
. . . . 245 | end | |
93 0.00 0.00 0.00 246 | end | |
. . . . 247 | | |
544 129.60 129.60 0.00 248 | -- Function totals | |
1 0.00 0.00 0.00 248 | wget.callbacks.httploop_result = function(url, err, http_stat) | |
. . . . 249 | -- NEW for 2014: Slightly more verbose messages because people keep | |
. . . . 250 | -- complaining that it's not moving or not working | |
544 129.49 129.49 0.00 251 | local status_code = http_stat["statcode"] | |
. . . . 252 | | |
544 0.00 0.00 0.00 253 | url_count = url_count + 1 | |
544 0.02 0.02 0.00 254 | io.stdout:write(url_count .. "=" .. status_code .. " " .. url["url"] .. ". \r") | |
544 0.07 0.07 0.00 255 | io.stdout:flush() | |
. . . . 256 | | |
544 0.00 0.00 0.00 257 | if status_code >= 200 and status_code <= 399 then | |
506 0.00 0.00 0.00 258 | downloaded[url.url] = true | |
. . . . 259 | end | |
. . . . 260 | | |
. . . . 261 | -- consider 403 as banned from twitpic, not pernament failure | |
544 0.00 0.00 0.00 262 | if status_code >= 500 or | |
544 0.00 0.00 0.00 263 | (status_code >= 400 and status_code ~= 404 and status_code ~= 403) or | |
544 0.00 0.00 0.00 264 | (status_code == 403 and string.match(url["host"], "twitpic%.com")) then | |
. . . . 265 | if string.match(url["host"], "twitpic%.com") then | |
. . . . 266 | | |
. . . . 267 | if status_code == 403 then | |
. . . . 268 | return wget.actions.ABORT | |
. . . . 269 | end | |
. . . . 270 | | |
. . . . 271 | io.stdout:write("\nServer returned "..http_stat.statcode.." for " .. url["url"] .. ". Sleeping.\n") | |
. . . . 272 | io.stdout:flush() | |
. . . . 273 | | |
. . . . 274 | os.execute("sleep 10") | |
. . . . 275 | | |
. . . . 276 | tries = tries + 1 | |
. . . . 277 | | |
. . . . 278 | if tries >= 5 then | |
. . . . 279 | return wget.actions.ABORT | |
. . . . 280 | else | |
. . . . 281 | return wget.actions.CONTINUE | |
. . . . 282 | end | |
. . . . 283 | else | |
. . . . 284 | io.stdout:write("\nServer returned "..http_stat.statcode.." for " .. url["url"] .. ". Sleeping.\n") | |
. . . . 285 | io.stdout:flush() | |
. . . . 286 | | |
. . . . 287 | os.execute("sleep 10") | |
. . . . 288 | | |
. . . . 289 | tries = tries + 1 | |
. . . . 290 | | |
. . . . 291 | if tries >= 5 then | |
. . . . 292 | return wget.actions.NOTHING | |
. . . . 293 | else | |
. . . . 294 | return wget.actions.CONTINUE | |
. . . . 295 | end | |
. . . . 296 | end | |
544 0.00 0.00 0.00 297 | elseif status_code == 0 then | |
. . . . 298 | io.stdout:write("\nServer returned "..http_stat.statcode.." for " .. url["url"] .. ". Sleeping.\n") | |
. . . . 299 | io.stdout:flush() | |
. . . . 300 | | |
. . . . 301 | os.execute("sleep 10") | |
. . . . 302 | | |
. . . . 303 | tries = tries + 1 | |
. . . . 304 | | |
. . . . 305 | if tries >= 5 then | |
. . . . 306 | return wget.actions.ABORT | |
. . . . 307 | else | |
. . . . 308 | return wget.actions.CONTINUE | |
. . . . 309 | end | |
. . . . 310 | end | |
. . . . 311 | | |
544 0.00 0.00 0.00 312 | tries = 0 | |
. . . . 313 | | |
. . . . 314 | -- We're okay; sleep a bit (if we have to) and continue | |
. . . . 315 | -- local sleep_time = 0.1 * (math.random(1000, 2000) / 100.0) | |
544 0.00 0.00 0.00 316 | local sleep_time = 0 | |
. . . . 317 | | |
. . . . 318 | -- if string.match(url["host"], "cdn") or string.match(url["host"], "media") then | |
. . . . 319 | -- -- We should be able to go fast on images since that's what a web browser does | |
. . . . 320 | -- sleep_time = 0 | |
. . . . 321 | -- end | |
. . . . 322 | | |
544 0.00 0.00 0.00 323 | if sleep_time > 0.001 then | |
. . . . 324 | os.execute("sleep " .. sleep_time) | |
. . . . 325 | end | |
. . . . 326 | | |
544 0.00 0.00 0.00 327 | return wget.actions.NOTHING | |
1 0.00 0.00 0.00 328 | end | |
============================================================================================================================================================================= | |
Hits Total Self Child Line | urlcode.lua - Times in seconds | |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
. . . . 1 | ---------------------------------------------------------------------------- | |
. . . . 2 | -- Utility functions for encoding/decoding of URLs. | |
. . . . 3 | -- | |
. . . . 4 | -- @release $Id: urlcode.lua,v 1.10 2008/01/21 16:11:32 carregal Exp $ | |
. . . . 5 | ---------------------------------------------------------------------------- | |
. . . . 6 | | |
1 0.03 0.03 0.00 7 | local ipairs, next, pairs, tonumber, type = ipairs, next, pairs, tonumber, type | |
1 0.00 0.00 0.00 8 | local string = string | |
1 0.00 0.00 0.00 9 | local table = table | |
. . . . 10 | | |
1 0.00 0.00 0.00 11 | module ("cgilua.urlcode") | |
. . . . 12 | | |
. . . . 13 | ---------------------------------------------------------------------------- | |
. . . . 14 | -- Decode an URL-encoded string (see RFC 2396) | |
. . . . 15 | ---------------------------------------------------------------------------- | |
1 0.00 0.00 0.00 16 | function unescape (str) | |
. . . . 17 | str = string.gsub (str, "+", " ") | |
. . . . 18 | str = string.gsub (str, "%%(%x%x)", function(h) return string.char(tonumber(h,16)) end) | |
. . . . 19 | str = string.gsub (str, "\r\n", "\n") | |
. . . . 20 | return str | |
1 0.00 0.00 0.00 21 | end | |
. . . . 22 | | |
. . . . 23 | ---------------------------------------------------------------------------- | |
. . . . 24 | -- URL-encode a string (see RFC 2396) | |
. . . . 25 | ---------------------------------------------------------------------------- | |
1 0.00 0.00 0.00 26 | function escape (str) | |
. . . . 27 | str = string.gsub (str, "\n", "\r\n") | |
. . . . 28 | str = string.gsub (str, "([^0-9a-zA-Z ])", -- locale independent | |
. . . . 29 | function (c) return string.format ("%%%02X", string.byte(c)) end) | |
. . . . 30 | str = string.gsub (str, " ", "+") | |
. . . . 31 | return str | |
1 0.00 0.00 0.00 32 | end | |
. . . . 33 | | |
. . . . 34 | ---------------------------------------------------------------------------- | |
. . . . 35 | -- Insert a (name=value) pair into table [[args]] | |
. . . . 36 | -- @param args Table to receive the result. | |
. . . . 37 | -- @param name Key for the table. | |
. . . . 38 | -- @param value Value for the key. | |
. . . . 39 | -- Multi-valued names will be represented as tables with numerical indexes | |
. . . . 40 | -- (in the order they came). | |
. . . . 41 | ---------------------------------------------------------------------------- | |
1 0.00 0.00 0.00 42 | function insertfield (args, name, value) | |
. . . . 43 | if not args[name] then | |
. . . . 44 | args[name] = value | |
. . . . 45 | else | |
. . . . 46 | local t = type (args[name]) | |
. . . . 47 | if t == "string" then | |
. . . . 48 | args[name] = { | |
. . . . 49 | args[name], | |
. . . . 50 | value, | |
. . . . 51 | } | |
. . . . 52 | elseif t == "table" then | |
. . . . 53 | table.insert (args[name], value) | |
. . . . 54 | else | |
. . . . 55 | error ("CGILua fatal error (invalid args table)!") | |
. . . . 56 | end | |
. . . . 57 | end | |
1 0.00 0.00 0.00 58 | end | |
. . . . 59 | | |
. . . . 60 | ---------------------------------------------------------------------------- | |
. . . . 61 | -- Parse url-encoded request data | |
. . . . 62 | -- (the query part of the script URL or url-encoded post data) | |
. . . . 63 | -- | |
. . . . 64 | -- Each decoded (name=value) pair is inserted into table [[args]] | |
. . . . 65 | -- @param query String to be parsed. | |
. . . . 66 | -- @param args Table where to store the pairs. | |
. . . . 67 | ---------------------------------------------------------------------------- | |
1 0.00 0.00 0.00 68 | function parsequery (query, args) | |
. . . . 69 | if type(query) == "string" then | |
. . . . 70 | local insertfield, unescape = insertfield, unescape | |
. . . . 71 | string.gsub (query, "([^&=]+)=([^&=]*)&?", | |
. . . . 72 | function (key, val) | |
. . . . 73 | insertfield (args, unescape(key), unescape(val)) | |
. . . . 74 | end) | |
. . . . 75 | end | |
1 0.00 0.00 0.00 76 | end | |
. . . . 77 | | |
. . . . 78 | ---------------------------------------------------------------------------- | |
. . . . 79 | -- URL-encode the elements of a table creating a string to be used in a | |
. . . . 80 | -- URL for passing data/parameters to another script | |
. . . . 81 | -- @param args Table where to extract the pairs (name=value). | |
. . . . 82 | -- @return String with the resulting encoding. | |
. . . . 83 | ---------------------------------------------------------------------------- | |
1 0.00 0.00 0.00 84 | function encodetable (args) | |
. . . . 85 | if args == nil or next(args) == nil then -- no args or empty args? | |
. . . . 86 | return "" | |
. . . . 87 | end | |
. . . . 88 | local strp = "" | |
. . . . 89 | for key, vals in pairs(args) do | |
. . . . 90 | if type(vals) ~= "table" then | |
. . . . 91 | vals = {vals} | |
. . . . 92 | end | |
. . . . 93 | for i,val in ipairs(vals) do | |
. . . . 94 | strp = strp.."&"..escape(key).."="..escape(val) | |
. . . . 95 | end | |
. . . . 96 | end | |
. . . . 97 | -- remove first & | |
. . . . 98 | return string.sub(strp,2) | |
2 0.00 0.00 0.00 99 | end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment