· 4 years ago · Jun 09, 2021, 10:56 PM
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
22local base = _G
23local decode_scanArray
24local decode_scanComment
25local decode_scanConstant
26local decode_scanNumber
27local decode_scanObject
28local decode_scanString
29local decode_scanWhitespace
30local encodeString
31local isArray
32local isEncodable
33
34local 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))
81end
82
83local 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)
109end
110
111local function null()
112 return null -- so json.null() will also return null ;-)
113end
114
115
116function 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
136end
137
138function 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
143end
144
145function 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)
156end
157
158function 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
171end
172
173function 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
201end
202
203function 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
231end
232
233function decode_scanWhitespace(s,startPos)
234 local whitespace=" \n\r\t"
235 local stringLen = string.len(s)
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
240end
241
242function 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
248end
249
250function 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
267end
268
269function 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)
272end
273
274
275
276
277-- Gist Client
278-- 2013 Matti Vapa
279-- License: MIT
280
281local url = "https://api.github.com/gists"
282
283-- default parameters for POST
284local putvars = {}
285putvars["description"] = ""
286putvars["public"] = true
287putvars["files"] = {}
288
289
290local printUsage = function()
291 print("Usage:")
292 print("gist get <id>")
293 print("gist put <filename1> <filename2> ...")
294end
295
296local args = {...}
297local mode
298
299if not http then
300 print( "gist requires http API" )
301 print( "Set enableAPI_http to 1 in mod_ComputerCraft.cfg" )
302 return
303end
304
305if #args == 2 and args[1] == "get" then
306 mode = "get"
307elseif args[1] == "put" and #args >= 2 then
308 mode = "put"
309else
310 printUsage()
311 return
312end
313
314if 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
367elseif 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
400end