Skip to content

Instantly share code, notes, and snippets.

@Cameron-D
Created September 15, 2014 12:47
Show Gist options
  • Save Cameron-D/c8cad4e518f8b9bc9760 to your computer and use it in GitHub Desktop.
Save Cameron-D/c8cad4e518f8b9bc9760 to your computer and use it in GitHub Desktop.
=============================================================================================================================================================================
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