SHOW:
|
|
- or go back to the newest paste.
| 1 | -- Gist Client for ComputerCraft | |
| 2 | -- version 1.1 | |
| 3 | -- Matti Vapa, 2013 | |
| 4 | -- License: MIT | |
| 5 | -- the client uses JSON4LUA module for parsing JSON (provided below) | |
| 6 | -- client code starts around line 270 | |
| 7 | ||
| 8 | -- v1.1 noticed that you don't need to escape single quotes, per json specifications | |
| 9 | -- v1.0 first version | |
| 10 | ||
| 11 | ||
| 12 | ----------------------------------------------------------------------------- | |
| 13 | -- JSON4Lua: JSON encoding / decoding support for the Lua language. | |
| 14 | -- json Module. | |
| 15 | -- Author: Craig Mason-Jones | |
| 16 | -- Homepage: http://json.luaforge.net/ | |
| 17 | -- Version: 0.9.40 | |
| 18 | -- This module is released under the MIT License (MIT). | |
| 19 | ||
| 20 | -- edited for brevity | |
| 21 | ||
| 22 | local base = _G | |
| 23 | local decode_scanArray | |
| 24 | local decode_scanComment | |
| 25 | local decode_scanConstant | |
| 26 | local decode_scanNumber | |
| 27 | local decode_scanObject | |
| 28 | local decode_scanString | |
| 29 | local decode_scanWhitespace | |
| 30 | local encodeString | |
| 31 | local isArray | |
| 32 | local isEncodable | |
| 33 | ||
| 34 | local function encode (v) | |
| 35 | -- Handle nil values | |
| 36 | if v==nil then | |
| 37 | return "null" | |
| 38 | end | |
| 39 | ||
| 40 | local vtype = base.type(v) | |
| 41 | ||
| 42 | -- Handle strings | |
| 43 | if vtype=='string' then | |
| 44 | return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string | |
| 45 | end | |
| 46 | ||
| 47 | -- Handle booleans | |
| 48 | if vtype=='number' or vtype=='boolean' then | |
| 49 | return base.tostring(v) | |
| 50 | end | |
| 51 | ||
| 52 | -- Handle tables | |
| 53 | if vtype=='table' then | |
| 54 | local rval = {}
| |
| 55 | -- Consider arrays separately | |
| 56 | local bArray, maxCount = isArray(v) | |
| 57 | if bArray then | |
| 58 | for i = 1,maxCount do | |
| 59 | table.insert(rval, encode(v[i])) | |
| 60 | end | |
| 61 | else -- An object, not an array | |
| 62 | for i,j in base.pairs(v) do | |
| 63 | if isEncodable(i) and isEncodable(j) then | |
| 64 | table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) | |
| 65 | end | |
| 66 | end | |
| 67 | end | |
| 68 | if bArray then | |
| 69 | return '[' .. table.concat(rval,',') ..']' | |
| 70 | else | |
| 71 | return '{' .. table.concat(rval,',') .. '}'
| |
| 72 | end | |
| 73 | end | |
| 74 | ||
| 75 | -- Handle null values | |
| 76 | if vtype=='function' and v==null then | |
| 77 | return 'null' | |
| 78 | end | |
| 79 | ||
| 80 | base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) | |
| 81 | end | |
| 82 | ||
| 83 | local function decode(s, startPos) | |
| 84 | startPos = startPos and startPos or 1 | |
| 85 | startPos = decode_scanWhitespace(s,startPos) | |
| 86 | base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') | |
| 87 | local curChar = string.sub(s,startPos,startPos) | |
| 88 | -- Object | |
| 89 | if curChar=='{' then
| |
| 90 | return decode_scanObject(s,startPos) | |
| 91 | end | |
| 92 | -- Array | |
| 93 | if curChar=='[' then | |
| 94 | return decode_scanArray(s,startPos) | |
| 95 | end | |
| 96 | -- Number | |
| 97 | if string.find("+-0123456789.e", curChar, 1, true) then
| |
| 98 | return decode_scanNumber(s,startPos) | |
| 99 | end | |
| 100 | -- String | |
| 101 | if curChar==[["]] or curChar==[[']] then | |
| 102 | return decode_scanString(s,startPos) | |
| 103 | end | |
| 104 | if string.sub(s,startPos,startPos+1)=='/*' then | |
| 105 | return decode(s, decode_scanComment(s,startPos)) | |
| 106 | end | |
| 107 | -- Otherwise, it must be a constant | |
| 108 | return decode_scanConstant(s,startPos) | |
| 109 | end | |
| 110 | ||
| 111 | local function null() | |
| 112 | return null -- so json.null() will also return null ;-) | |
| 113 | end | |
| 114 | ||
| 115 | ||
| 116 | function decode_scanArray(s,startPos) | |
| 117 | local array = {} -- The return value
| |
| 118 | local stringLen = string.len(s) | |
| 119 | base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) | |
| 120 | startPos = startPos + 1 | |
| 121 | -- Infinite loop for array elements | |
| 122 | repeat | |
| 123 | startPos = decode_scanWhitespace(s,startPos) | |
| 124 | base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') | |
| 125 | local curChar = string.sub(s,startPos,startPos) | |
| 126 | if (curChar==']') then | |
| 127 | return array, startPos+1 | |
| 128 | end | |
| 129 | if (curChar==',') then | |
| 130 | startPos = decode_scanWhitespace(s,startPos+1) | |
| 131 | end | |
| 132 | base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') | |
| 133 | object, startPos = decode(s,startPos) | |
| 134 | table.insert(array,object) | |
| 135 | until false | |
| 136 | end | |
| 137 | ||
| 138 | function decode_scanComment(s, startPos) | |
| 139 | base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) | |
| 140 | local endPos = string.find(s,'*/',startPos+2) | |
| 141 | base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) | |
| 142 | return endPos+2 | |
| 143 | end | |
| 144 | ||
| 145 | function decode_scanConstant(s, startPos) | |
| 146 | local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
| |
| 147 | local constNames = {"true","false","null"}
| |
| 148 | ||
| 149 | for i,k in base.pairs(constNames) do | |
| 150 | --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
| |
| 151 | if string.sub(s,startPos, startPos + string.len(k) -1 )==k then | |
| 152 | return consts[k], startPos + string.len(k) | |
| 153 | end | |
| 154 | end | |
| 155 | base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) | |
| 156 | end | |
| 157 | ||
| 158 | function decode_scanNumber(s,startPos) | |
| 159 | local endPos = startPos+1 | |
| 160 | local stringLen = string.len(s) | |
| 161 | local acceptableChars = "+-0123456789.e" | |
| 162 | while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) | |
| 163 | and endPos<=stringLen | |
| 164 | ) do | |
| 165 | endPos = endPos + 1 | |
| 166 | end | |
| 167 | local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) | |
| 168 | local stringEval = base.loadstring(stringValue) | |
| 169 | base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) | |
| 170 | return stringEval(), endPos | |
| 171 | end | |
| 172 | ||
| 173 | function decode_scanObject(s,startPos) | |
| 174 | local object = {}
| |
| 175 | local stringLen = string.len(s) | |
| 176 | local key, value | |
| 177 | base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
| |
| 178 | startPos = startPos + 1 | |
| 179 | repeat | |
| 180 | startPos = decode_scanWhitespace(s,startPos) | |
| 181 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') | |
| 182 | local curChar = string.sub(s,startPos,startPos) | |
| 183 | if (curChar=='}') then | |
| 184 | return object,startPos+1 | |
| 185 | end | |
| 186 | if (curChar==',') then | |
| 187 | startPos = decode_scanWhitespace(s,startPos+1) | |
| 188 | end | |
| 189 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') | |
| 190 | -- Scan the key | |
| 191 | key, startPos = decode(s,startPos) | |
| 192 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
| 193 | startPos = decode_scanWhitespace(s,startPos) | |
| 194 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
| 195 | base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) | |
| 196 | startPos = decode_scanWhitespace(s,startPos+1) | |
| 197 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
| 198 | value, startPos = decode(s,startPos) | |
| 199 | object[key]=value | |
| 200 | until false -- infinite loop while key-value pairs are found | |
| 201 | end | |
| 202 | ||
| 203 | function decode_scanString(s,startPos) | |
| 204 | base.assert(startPos, 'decode_scanString(..) called without start position') | |
| 205 | local startChar = string.sub(s,startPos,startPos) | |
| 206 | base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') | |
| 207 | local escaped = false | |
| 208 | local endPos = startPos + 1 | |
| 209 | local bEnded = false | |
| 210 | local stringLen = string.len(s) | |
| 211 | repeat | |
| 212 | local curChar = string.sub(s,endPos,endPos) | |
| 213 | -- Character escaping is only used to escape the string delimiters | |
| 214 | if not escaped then | |
| 215 | if curChar==[[\]] then | |
| 216 | escaped = true | |
| 217 | else | |
| 218 | bEnded = curChar==startChar | |
| 219 | end | |
| 220 | else | |
| 221 | -- If we're escaped, we accept the current character come what may | |
| 222 | escaped = false | |
| 223 | end | |
| 224 | endPos = endPos + 1 | |
| 225 | base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) | |
| 226 | until bEnded | |
| 227 | local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) | |
| 228 | local stringEval = base.loadstring(stringValue) | |
| 229 | base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) | |
| 230 | return stringEval(), endPos | |
| 231 | end | |
| 232 | ||
| 233 | function decode_scanWhitespace(s,startPos) | |
| 234 | local whitespace=" \n\r\t" | |
| 235 | - | s = string.gsub(s,"'",'\\"') |
| 235 | + | |
| 236 | while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do | |
| 237 | startPos = startPos + 1 | |
| 238 | end | |
| 239 | return startPos | |
| 240 | end | |
| 241 | ||
| 242 | function encodeString(s) | |
| 243 | s = string.gsub(s,'\\','\\\\') | |
| 244 | s = string.gsub(s,'"','\\"') | |
| 245 | s = string.gsub(s,'\n','\\n') | |
| 246 | s = string.gsub(s,'\t','\\t') | |
| 247 | return s | |
| 248 | end | |
| 249 | ||
| 250 | function isArray(t) | |
| 251 | -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable | |
| 252 | -- (with the possible exception of 'n') | |
| 253 | local maxIndex = 0 | |
| 254 | for k,v in base.pairs(t) do | |
| 255 | if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair | |
| 256 | if (not isEncodable(v)) then return false end -- All array elements must be encodable | |
| 257 | maxIndex = math.max(maxIndex,k) | |
| 258 | else | |
| 259 | if (k=='n') then | |
| 260 | if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements | |
| 261 | else -- Else of (k=='n') | |
| 262 | if isEncodable(v) then return false end | |
| 263 | end -- End of (k~='n') | |
| 264 | end -- End of k,v not an indexed pair | |
| 265 | end -- End of loop across all pairs | |
| 266 | return true, maxIndex | |
| 267 | end | |
| 268 | ||
| 269 | function isEncodable(o) | |
| 270 | local t = base.type(o) | |
| 271 | return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) | |
| 272 | end | |
| 273 | ||
| 274 | ||
| 275 | ||
| 276 | ||
| 277 | -- Gist Client | |
| 278 | -- 2013 Matti Vapa | |
| 279 | -- License: MIT | |
| 280 | ||
| 281 | local url = "https://api.github.com/gists" | |
| 282 | ||
| 283 | -- default parameters for POST | |
| 284 | local putvars = {}
| |
| 285 | putvars["description"] = "" | |
| 286 | putvars["public"] = true | |
| 287 | putvars["files"] = {}
| |
| 288 | ||
| 289 | ||
| 290 | local printUsage = function() | |
| 291 | print("Usage:")
| |
| 292 | print("gist get <id>")
| |
| 293 | print("gist put <filename1> <filename2> ...")
| |
| 294 | end | |
| 295 | ||
| 296 | - | if #args == 2 and args[1] == "get"then |
| 296 | + | |
| 297 | local mode | |
| 298 | ||
| 299 | if not http then | |
| 300 | print( "gist requires http API" ) | |
| 301 | print( "Set enableAPI_http to 1 in mod_ComputerCraft.cfg" ) | |
| 302 | return | |
| 303 | end | |
| 304 | ||
| 305 | if #args == 2 and args[1] == "get" then | |
| 306 | mode = "get" | |
| 307 | elseif args[1] == "put" and #args >= 2 then | |
| 308 | mode = "put" | |
| 309 | else | |
| 310 | printUsage() | |
| 311 | return | |
| 312 | end | |
| 313 | ||
| 314 | if mode == "get" then | |
| 315 | ||
| 316 | local id = args[2] | |
| 317 | ||
| 318 | local resp = http.get(url.."/"..id) | |
| 319 | ||
| 320 | if resp ~= nil then | |
| 321 | --print("Success with code "..tostring(resp.getResponseCode()).."!")
| |
| 322 | local sresp = resp.readAll() | |
| 323 | resp.close() | |
| 324 | local data = decode(sresp) | |
| 325 | --iterate over the files (there can be several in one gist) | |
| 326 | for key, value in pairs(data["files"]) do | |
| 327 | local file = value["filename"] | |
| 328 | local localFile = file | |
| 329 | local path = shell.resolve(localFile) | |
| 330 | local confirm = true | |
| 331 | while fs.exists(path) do | |
| 332 | term.write("Local file "..localFile.." already exists. Overwrite? [y/n] ")
| |
| 333 | local inp = io.read():lower() | |
| 334 | if inp ~= "y" then | |
| 335 | term.write("Download to a new local file? [y/n] ")
| |
| 336 | local inp = io.read():lower() | |
| 337 | if inp ~= "y" then | |
| 338 | print("Skipping remote file: "..file)
| |
| 339 | confirm = false | |
| 340 | break | |
| 341 | else | |
| 342 | term.write("Give a new file name: ")
| |
| 343 | localFile = io.read() | |
| 344 | path = shell.resolve(localFile) | |
| 345 | end | |
| 346 | else | |
| 347 | print("Overwriting local file: "..localFile)
| |
| 348 | break | |
| 349 | end | |
| 350 | end | |
| 351 | if confirm then | |
| 352 | local raw = http.get(value["raw_url"]) | |
| 353 | if raw == nil then print("Unable to download contents of "..file.."!") return end
| |
| 354 | local f = fs.open(path,"w") | |
| 355 | f.write(raw.readAll()) | |
| 356 | f.close() | |
| 357 | raw.close() | |
| 358 | print("Remote file "..file.." downloaded!")
| |
| 359 | end | |
| 360 | end | |
| 361 | ||
| 362 | else | |
| 363 | print("Failed to download gist with id "..id.."!")
| |
| 364 | return | |
| 365 | end | |
| 366 | ||
| 367 | elseif mode == "put" then | |
| 368 | local files = {}
| |
| 369 | for i = 2,#args do | |
| 370 | local file = args[i] | |
| 371 | local path = shell.resolve(file) | |
| 372 | if not fs.exists(path) then | |
| 373 | print("No such file: "..file)
| |
| 374 | return | |
| 375 | end | |
| 376 | local f = fs.open(path,"r") | |
| 377 | files[file] = {}
| |
| 378 | files[file]["content"] = f.readAll() | |
| 379 | f.close() | |
| 380 | end | |
| 381 | ||
| 382 | putvars["files"] = files | |
| 383 | ||
| 384 | print("Give a description for the gist. (Can be blank)")
| |
| 385 | putvars["description"] = io.read() | |
| 386 | ||
| 387 | term.write("Uploading to gist... ")
| |
| 388 | local resp = http.post(url,encode(putvars)) | |
| 389 | ||
| 390 | if resp ~= nil then | |
| 391 | print("Success!")
| |
| 392 | --print("Success with code "..tostring(resp.getResponseCode()).."!")
| |
| 393 | local data = decode(resp.readAll()) | |
| 394 | resp.close() | |
| 395 | print("Gist id: "..tostring(data["id"])..".")
| |
| 396 | print("Available for viewing at https://gist.github.com/"..tostring(data["id"]))
| |
| 397 | else | |
| 398 | print("Failed.")
| |
| 399 | end | |
| 400 | end |