GrooveypenguinX

MEWarehouse2

Dec 27th, 2022 (edited)
33
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 53.70 KB | None | 0 0
  1. -----------------------------------------------------------------------------
  2. -- JSON4Lua: JSON encoding / decoding / traversing support for the Lua language.
  3. -- json Module.
  4. -- Authors: Craig Mason-Jones, Egor Skriptunoff
  5.  
  6. -- Version: 1.2.1
  7. -- 2017-05-10
  8.  
  9. -- This module is released under the MIT License (MIT).
  10. -- Please see LICENCE.txt for details:
  11. -- https://github.com/craigmj/json4lua/blob/master/doc/LICENCE.txt
  12. --
  13. -- USAGE:
  14. -- This module exposes three functions:
  15. -- json.encode(obj)
  16. -- Accepts Lua value (table/string/boolean/number/nil/json.null/json.empty) and returns JSON string.
  17. -- json.decode(s)
  18. -- Accepts JSON (as string or as loader function) and returns Lua object.
  19. -- json.traverse(s, callback)
  20. -- Accepts JSON (as string or as loader function) and user-supplied callback function, returns nothing
  21. -- Traverses the JSON, sends each item to callback function, no memory-consuming Lua objects are being created.
  22.  
  23. --
  24. -- REQUIREMENTS:
  25. -- Lua 5.1, Lua 5.2, Lua 5.3 or LuaJIT
  26. --
  27. -- CHANGELOG
  28. -- 1.2.1 Now you can partially decode JSON while traversing it (callback function should return true).
  29. -- 1.2.0 Some improvements made to be able to use this module on RAM restricted devices:
  30. -- To read large JSONs, you can now provide "loader function" instead of preloading whole JSON as Lua string.
  31. -- Added json.traverse() to traverse JSON using callback function (without creating arrays/objects in Lua).
  32. -- Now, instead of decoding whole JSON, you can decode its arbitrary element (e.g, array or object)
  33. -- by specifying the position where this element starts.
  34. -- In order to do that, at first you have to traverse JSON to get all positions you need.
  35. -- Most of the code rewritten to improve performance.
  36. -- Decoder now understands extended syntax beyond strict JSON standard:
  37. -- In arrays:
  38. -- trailing comma is ignored: [1,2,3,] -> [1,2,3]
  39. -- missing values are nulls: [,,42,,] -> [null,null,42,null]
  40. -- In objects:
  41. -- missing values are ignored: {,,"a":42,,} -> {"a":42}
  42. -- unquoted identifiers are valid keys: {a$b_5:42} -> {"a$b_5":42}
  43. -- Encoder now accepts both 0-based and 1-based Lua arrays (but decoder always converts JSON arrays to 1-based Lua arrays).
  44. -- Some minor bugs fixed.
  45. -- 1.1.0 Modifications made by Egor Skriptunoff, based on version 1.0.0 taken from
  46. -- https://github.com/craigmj/json4lua/blob/40fb13b0ec4a70e36f88812848511c5867bed857/json/json.lua.
  47. -- Added Lua 5.2 and Lua 5.3 compatibility.
  48. -- Removed Lua 5.0 compatibility.
  49. -- Introduced json.empty (Lua counterpart for empty JSON object)
  50. -- Bugs fixed:
  51. -- Attempt to encode Lua table {[10^9]=0} raises an out-of-memory error.
  52. -- Zero bytes '\0' in Lua strings are not escaped by encoder.
  53. -- JSON numbers with capital "E" (as in 1E+100) are not accepted by decoder.
  54. -- All nulls in a JSON arrays are skipped by decoder, sparse arrays could not be loaded correctly.
  55. -- UTF-16 surrogate pairs in JSON strings are not recognised by decoder.
  56. -- 1.0.0 Merged Amr Hassan's changes
  57. -- 0.9.30 Changed to MIT Licence.
  58. -- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
  59. -- Fixed Lua 5.1 compatibility issues.
  60. -- Introduced json.null to have null values in associative arrays.
  61. -- json.encode() performance improvement (more than 50%) through table_concat rather than ..
  62. -- Introduced decode ability to ignore /**/ comments in the JSON string.
  63. -- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
  64. -- 0.9.00 First release
  65. --
  66. -----------------------------------------------------------------------------
  67.  
  68. -----------------------------------------------------------------------------
  69. -- Module declaration
  70. -----------------------------------------------------------------------------
  71. local json = {}
  72.  
  73. do
  74. -----------------------------------------------------------------------------
  75. -- Imports and dependencies
  76. -----------------------------------------------------------------------------
  77. local math, string, table = require'math', require'string', require'table'
  78. local math_floor, math_max, math_type = math.floor, math.max, math.type or function() end
  79. local string_char, string_sub, string_find, string_match, string_gsub, string_format
  80. = string.char, string.sub, string.find, string.match, string.gsub, string.format
  81. local table_insert, table_remove, table_concat = table.insert, table.remove, table.concat
  82. local type, tostring, pairs, assert, error = type, tostring, pairs, assert, error
  83. local loadstring = loadstring or load
  84.  
  85. -----------------------------------------------------------------------------
  86. -- Public functions
  87. -----------------------------------------------------------------------------
  88. -- function json.encode(obj) encodes Lua value to JSON, returns JSON as string.
  89. -- function json.decode(s, pos) decodes JSON, returns the decoded result as Lua value (may be very memory-consuming).
  90.  
  91. -- Both functions json.encode() and json.decode() work with "special" Lua values json.null and json.empty
  92. -- special Lua value json.null = JSON value null
  93. -- special Lua value json.empty = JSON value {} (empty JSON object)
  94. -- regular Lua empty table = JSON value [] (empty JSON array)
  95.  
  96. -- Empty JSON objects and JSON nulls require special handling upon sending (encoding).
  97. -- Please make sure that you send empty JSON objects as json.empty (instead of empty Lua table).
  98. -- Empty Lua tables will be encoded as empty JSON arrays, not as empty JSON objects!
  99. -- json.encode( {empt_obj = json.empty, empt_arr = {}} ) --> {"empt_obj":{},"empt_arr":[]}
  100. -- Also make sure you send JSON nulls as json.null (instead of nil).
  101. -- json.encode( {correct = json.null, incorrect = nil} ) --> {"correct":null}
  102.  
  103. -- Empty JSON objects and JSON nulls require special handling upon receiving (decoding).
  104. -- After receiving the result of decoding, every Lua table returned (including nested tables) should firstly
  105. -- be compared with special Lua values json.empty/json.null prior to making operations on these values.
  106. -- If you don't need to distinguish between empty JSON objects and empty JSON arrays,
  107. -- json.empty may be replaced with newly created regular empty Lua table.
  108. -- v = (v == json.empty) and {} or v
  109. -- If you don't need special handling of JSON nulls, you may replace json.null with nil to make them disappear.
  110. -- if v == json.null then v = nil end
  111.  
  112. -- Function json.traverse(s, callback, pos) traverses JSON using user-supplied callback function, returns nothing.
  113. -- Traverse is useful to reduce memory usage: no memory-consuming objects are being created in Lua while traversing.
  114. -- Each item found inside JSON will be sent to callback function passing the following arguments:
  115. -- (path, json_type, value, pos, pos_last)
  116. -- path is array of nested JSON identifiers/indices, "path" is empty for root JSON element
  117. -- json_type is one of "null"/"boolean"/"number"/"string"/"array"/"object"
  118. -- value is defined when json_type is "null"/"boolean"/"number"/"string", value == nil for "object"/"array"
  119. -- pos is 1-based index of first character of current JSON element
  120. -- pos_last is 1-based index of last character of current JSON element (defined only when "value" ~= nil)
  121. -- "path" table reference is the same on each callback invocation, but its content differs every time.
  122. -- Do not modify "path" array inside your callback function, use it as read-only.
  123. -- Do not save reference to "path" for future use (create shallow table copy instead).
  124. -- callback function should return a value, when it is invoked with argument "value" == nil
  125. -- a truthy value means user wants to decode this JSON object/array and create its Lua counterpart (this may be memory-consuming)
  126. -- a falsy value (or no value returned) means user wants to traverse through this JSON object/array
  127. -- (returned value is ignored when callback function is invoked with value ~= nil)
  128.  
  129. -- Traverse examples:
  130.  
  131. -- json.traverse([[ 42 ]], callback)
  132. -- will invoke callback 1 time:
  133. -- path json_type value pos pos_last
  134. -- ---------- --------- -------------- --- --------
  135. -- callback( {}, "number", 42, 2, 3 )
  136. --
  137. -- json.traverse([[ {"a":true, "b":null, "c":["one","two"], "d":{ "e":{}, "f":[] } } ]], callback)
  138. -- will invoke callback 9 times:
  139. -- path json_type value pos pos_last
  140. -- ---------- --------- -------------- --- --------
  141. -- callback( {}, "object", nil, 2, nil )
  142. -- callback( {"a"}, "boolean", true, 7, 10 )
  143. -- callback( {"b"}, "null", json.null, 17, 20 ) -- special Lua value for JSON null
  144. -- callback( {"c"}, "array", nil, 27, nil )
  145. -- callback( {"c", 1}, "string", "one", 28, 32 )
  146. -- callback( {"c", 2}, "string", "two", 34, 38 )
  147. -- callback( {"d"}, "object", nil, 46, nil )
  148. -- callback( {"d", "e"}, "object", nil, 52, nil )
  149. -- callback( {"d", "f"}, "array", nil, 60, nil )
  150. --
  151. -- json.traverse([[ {"a":true, "b":null, "c":["one","two"], "d":{ "e":{}, "f":[] } } ]], callback)
  152. -- will invoke callback 9 times if callback returns true when invoked for array "c" and object "e":
  153. -- path json_type value pos pos_last
  154. -- ---------- --------- -------------- --- --------
  155. -- callback( {}, "object", nil, 2, nil )
  156. -- callback( {"a"}, "boolean", true, 7, 10 )
  157. -- callback( {"b"}, "null", json.null, 17, 20 )
  158. -- callback( {"c"}, "array", nil, 27, nil ) -- this callback returned true (user wants to decode this array)
  159. -- callback( {"c"}, "array", {"one", "two"}, 27, 39 ) -- the next invocation brings the result of decoding
  160. -- callback( {"d"}, "object", nil, 46, nil )
  161. -- callback( {"d", "e"}, "object", nil, 52, nil ) -- this callback returned true (user wants to decode this object)
  162. -- callback( {"d", "e"}, "object", json.empty, 52, 53 ) -- the next invocation brings the result of decoding (special Lua value for empty JSON object)
  163. -- callback( {"d", "f"}, "array", nil, 60, nil )
  164.  
  165.  
  166. -- Both decoder functions json.decode(s) and json.traverse(s, callback) can accept JSON (argument s)
  167. -- as a "loader function" instead of a string.
  168. -- This function will be called repeatedly to return next parts (substrings) of JSON.
  169. -- An empty string, nil, or no value returned from "loader function" means the end of JSON.
  170. -- This may be useful for low-memory devices or for traversing huge JSON files.
  171.  
  172.  
  173. --- The json.null table allows one to specify a null value in an associative array (which is otherwise
  174. -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
  175. local null = {"This Lua table is used to designate JSON null value, compare your values with json.null to determine JSON nulls"}
  176. json.null = setmetatable(null, {
  177. __tostring = function() return 'null' end
  178. })
  179.  
  180. --- The json.empty table allows one to specify an empty JSON object.
  181. -- To encode empty JSON array use usual empty Lua table.
  182. -- Example: t = { empty_object=json.empty, empty_array={} }
  183. local empty = {}
  184. json.empty = setmetatable(empty, {
  185. __tostring = function() return '{}' end,
  186. __newindex = function() error("json.empty is an read-only Lua table", 2) end
  187. })
  188.  
  189. -----------------------------------------------------------------------------
  190. -- Private functions
  191. -----------------------------------------------------------------------------
  192. local decode
  193. local decode_scanArray
  194. local decode_scanConstant
  195. local decode_scanNumber
  196. local decode_scanObject
  197. local decode_scanString
  198. local decode_scanIdentifier
  199. local decode_scanWhitespace
  200. local encodeString
  201. local isArray
  202. local isEncodable
  203. local isConvertibleToString
  204. local isRegularNumber
  205.  
  206. -----------------------------------------------------------------------------
  207. -- PUBLIC FUNCTIONS
  208. -----------------------------------------------------------------------------
  209. --- Encodes an arbitrary Lua object / variable.
  210. -- @param obj Lua value (table/string/boolean/number/nil/json.null/json.empty) to be JSON-encoded.
  211. -- @return string String containing the JSON encoding.
  212. function json.encode(obj)
  213. -- Handle nil and null values
  214. if obj == nil or obj == null then
  215. return 'null'
  216. end
  217.  
  218. -- Handle empty JSON object
  219. if obj == empty then
  220. return '{}'
  221. end
  222.  
  223. local obj_type = type(obj)
  224.  
  225. -- Handle strings
  226. if obj_type == 'string' then
  227. return '"'..encodeString(obj)..'"'
  228. end
  229.  
  230. -- Handle booleans
  231. if obj_type == 'boolean' then
  232. return tostring(obj)
  233. end
  234.  
  235. -- Handle numbers
  236. if obj_type == 'number' then
  237. assert(isRegularNumber(obj), 'numeric values Inf and NaN are unsupported')
  238. return math_type(obj) == 'integer' and tostring(obj) or string_format('%.17g', obj)
  239. end
  240.  
  241. -- Handle tables
  242. if obj_type == 'table' then
  243. local rval = {}
  244. -- Consider arrays separately
  245. local bArray, maxCount = isArray(obj)
  246. if bArray then
  247. for i = obj[0] ~= nil and 0 or 1, maxCount do
  248. table_insert(rval, json.encode(obj[i]))
  249. end
  250. else -- An object, not an array
  251. for i, j in pairs(obj) do
  252. if isConvertibleToString(i) and isEncodable(j) then
  253. table_insert(rval, '"'..encodeString(i)..'":'..json.encode(j))
  254. end
  255. end
  256. end
  257. if bArray then
  258. return '['..table_concat(rval, ',')..']'
  259. else
  260. return '{'..table_concat(rval, ',')..'}'
  261. end
  262. end
  263.  
  264. error('Unable to JSON-encode Lua value of unsupported type "'..obj_type..'": '..tostring(obj))
  265. end
  266.  
  267. local function create_state(s)
  268. -- Argument s may be "whole JSON string" or "JSON loader function"
  269. -- Returns "state" object which holds current state of reading long JSON:
  270. -- part = current part (substring of long JSON string)
  271. -- disp = number of bytes before current part inside long JSON
  272. -- more = function to load next substring (more == nil if all substrings are already read)
  273. local state = {disp = 0}
  274. if type(s) == "string" then
  275. -- s is whole JSON string
  276. state.part = s
  277. else
  278. -- s is loader function
  279. state.part = ""
  280. state.more = s
  281. end
  282. return state
  283. end
  284.  
  285. --- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
  286. -- @param s The string to scan (or "loader function" for getting next substring).
  287. -- @param pos (optional) The position inside s to start scan, default = 1.
  288. -- @return Lua object The object that was scanned, as a Lua table / string / number / boolean / json.null / json.empty.
  289. function json.decode(s, pos)
  290. return (decode(create_state(s), pos or 1))
  291. end
  292.  
  293. --- Traverses a JSON string, sends everything to user-supplied callback function, returns nothing
  294. -- @param s The string to scan (or "loader function" for getting next substring).
  295. -- @param callback The user-supplied callback function which accepts arguments (path, json_type, value, pos, pos_last).
  296. -- @param pos (optional) The position inside s to start scan, default = 1.
  297. function json.traverse(s, callback, pos)
  298. decode(create_state(s), pos or 1, {path = {}, callback = callback})
  299. end
  300.  
  301. local function read_ahead(state, startPos)
  302. -- Make sure there are at least 32 bytes read ahead
  303. local endPos = startPos + 31
  304. local part = state.part -- current part (substring of "whole JSON" string)
  305. local disp = state.disp -- number of bytes before current part inside "whole JSON" string
  306. local more = state.more -- function to load next substring
  307. assert(startPos > disp)
  308. while more and disp + #part < endPos do
  309. -- (disp + 1) ... (disp + #part) - we already have this segment now
  310. -- startPos ... endPos - we need to have this segment
  311. local next_substr = more()
  312. if not next_substr or next_substr == "" then
  313. more = nil
  314. else
  315. disp, part = disp + #part, string_sub(part, startPos - disp)
  316. disp, part = disp - #part, part..next_substr
  317. end
  318. end
  319. state.disp, state.part, state.more = disp, part, more
  320. end
  321.  
  322. local function get_word(state, startPos, length)
  323. -- 1 <= length <= 32
  324. if state.more then read_ahead(state, startPos) end
  325. local idx = startPos - state.disp
  326. return string_sub(state.part, idx, idx + length - 1)
  327. end
  328.  
  329. local function skip_until_word(state, startPos, word)
  330. -- #word < 30
  331. -- returns position after that word (nil if not found)
  332. repeat
  333. if state.more then read_ahead(state, startPos) end
  334. local part, disp = state.part, state.disp
  335. local b, e = string_find(part, word, startPos - disp, true)
  336. if b then
  337. return disp + e + 1
  338. end
  339. startPos = disp + #part + 2 - #word
  340. until not state.more
  341. end
  342.  
  343. local function match_with_pattern(state, startPos, pattern, operation)
  344. -- pattern must be
  345. -- "^[some set of chars]+"
  346. -- returns
  347. -- matched_string, endPos for operation "read" (matched_string == "" if no match found)
  348. -- endPos for operation "skip"
  349. if operation == "read" then
  350. local t = {}
  351. repeat
  352. if state.more then read_ahead(state, startPos) end
  353. local part, disp = state.part, state.disp
  354. local str = string_match(part, pattern, startPos - disp)
  355. if str then
  356. table_insert(t, str)
  357. startPos = startPos + #str
  358. end
  359. until not str or startPos <= disp + #part
  360. return table_concat(t), startPos
  361. elseif operation == "skip" then
  362. repeat
  363. if state.more then read_ahead(state, startPos) end
  364. local part, disp = state.part, state.disp
  365. local b, e = string_find(part, pattern, startPos - disp)
  366. if b then
  367. startPos = startPos + e - b + 1
  368. end
  369. until not b or startPos <= disp + #part
  370. return startPos
  371. else
  372. error("Wrong operation name")
  373. end
  374. end
  375.  
  376. --- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
  377. -- @param state The state of JSON reader.
  378. -- @param startPos Starting position where the JSON string is located.
  379. -- @param traverse (optional) table with fields "path" and "callback" for traversing JSON.
  380. -- @param decode_key (optional) boolean flag for decoding key inside JSON object.
  381. -- @return Lua_object,int The object that was scanned, as a Lua table / string / number / boolean / json.null / json.empty,
  382. -- and the position of the first character after the scanned JSON object.
  383. function decode(state, startPos, traverse, decode_key)
  384. local curChar, value, nextPos
  385. startPos, curChar = decode_scanWhitespace(state, startPos)
  386. if curChar == '{' and not decode_key then
  387. -- Object
  388. if traverse and traverse.callback(traverse.path, "object", nil, startPos, nil) then
  389. -- user wants to decode this JSON object (and get it as Lua value) while traversing
  390. local object, endPos = decode_scanObject(state, startPos)
  391. traverse.callback(traverse.path, "object", object, startPos, endPos - 1)
  392. return false, endPos
  393. end
  394. return decode_scanObject(state, startPos, traverse)
  395. elseif curChar == '[' and not decode_key then
  396. -- Array
  397. if traverse and traverse.callback(traverse.path, "array", nil, startPos, nil) then
  398. -- user wants to decode this JSON array (and get it as Lua value) while traversing
  399. local array, endPos = decode_scanArray(state, startPos)
  400. traverse.callback(traverse.path, "array", array, startPos, endPos - 1)
  401. return false, endPos
  402. end
  403. return decode_scanArray(state, startPos, traverse)
  404. elseif curChar == '"' then
  405. -- String
  406. value, nextPos = decode_scanString(state, startPos)
  407. if traverse then
  408. traverse.callback(traverse.path, "string", value, startPos, nextPos - 1)
  409. end
  410. elseif decode_key then
  411. -- Unquoted string as key name
  412. return decode_scanIdentifier(state, startPos)
  413. elseif string_find(curChar, "^[%d%-]") then
  414. -- Number
  415. value, nextPos = decode_scanNumber(state, startPos)
  416. if traverse then
  417. traverse.callback(traverse.path, "number", value, startPos, nextPos - 1)
  418. end
  419. else
  420. -- Otherwise, it must be a constant
  421. value, nextPos = decode_scanConstant(state, startPos)
  422. if traverse then
  423. traverse.callback(traverse.path, value == null and "null" or "boolean", value, startPos, nextPos - 1)
  424. end
  425. end
  426. return value, nextPos
  427. end
  428.  
  429. -----------------------------------------------------------------------------
  430. -- Internal, PRIVATE functions.
  431. -- Following a Python-like convention, I have prefixed all these 'PRIVATE'
  432. -- functions with an underscore.
  433. -----------------------------------------------------------------------------
  434.  
  435. --- Scans an array from JSON into a Lua object
  436. -- startPos begins at the start of the array.
  437. -- Returns the array and the next starting position
  438. -- @param state The state of JSON reader.
  439. -- @param startPos The starting position for the scan.
  440. -- @param traverse (optional) table with fields "path" and "callback" for traversing JSON.
  441. -- @return table,int The scanned array as a table, and the position of the next character to scan.
  442. function decode_scanArray(state, startPos, traverse)
  443. local array = not traverse and {} -- The return value
  444. local elem_index, elem_ready, object = 1
  445. startPos = startPos + 1
  446. -- Infinite loop for array elements
  447. while true do
  448. repeat
  449. local curChar
  450. startPos, curChar = decode_scanWhitespace(state, startPos)
  451. if curChar == ']' then
  452. return array, startPos + 1
  453. elseif curChar == ',' then
  454. if not elem_ready then
  455. -- missing value in JSON array
  456. if traverse then
  457. table_insert(traverse.path, elem_index)
  458. traverse.callback(traverse.path, "null", null, startPos, startPos - 1) -- empty substring: pos_last = pos - 1
  459. table_remove(traverse.path)
  460. else
  461. array[elem_index] = null
  462. end
  463. end
  464. elem_ready = false
  465. elem_index = elem_index + 1
  466. startPos = startPos + 1
  467. end
  468. until curChar ~= ','
  469. if elem_ready then
  470. error('Comma is missing in JSON array at position '..startPos)
  471. end
  472. if traverse then
  473. table_insert(traverse.path, elem_index)
  474. end
  475. object, startPos = decode(state, startPos, traverse)
  476. if traverse then
  477. table_remove(traverse.path)
  478. else
  479. array[elem_index] = object
  480. end
  481. elem_ready = true
  482. end
  483. end
  484.  
  485. --- Scans for given constants: true, false or null
  486. -- Returns the appropriate Lua type, and the position of the next character to read.
  487. -- @param state The state of JSON reader.
  488. -- @param startPos The position in the string at which to start scanning.
  489. -- @return object, int The object (true, false or json.null) and the position at which the next character should be scanned.
  490. function decode_scanConstant(state, startPos)
  491. local w5 = get_word(state, startPos, 5)
  492. local w4 = string_sub(w5, 1, 4)
  493. if w5 == "false" then
  494. return false, startPos + 5
  495. elseif w4 == "true" then
  496. return true, startPos + 4
  497. elseif w4 == "null" then
  498. return null, startPos + 4
  499. end
  500. error('Failed to parse JSON at position '..startPos)
  501. end
  502.  
  503. --- Scans a number from the JSON encoded string.
  504. -- (in fact, also is able to scan numeric +- eqns, which is not in the JSON spec.)
  505. -- Returns the number, and the position of the next character after the number.
  506. -- @param state The state of JSON reader.
  507. -- @param startPos The position at which to start scanning.
  508. -- @return number,int The extracted number and the position of the next character to scan.
  509. function decode_scanNumber(state, startPos)
  510. local stringValue, endPos = match_with_pattern(state, startPos, '^[%+%-%d%.eE]+', "read")
  511. local stringEval = loadstring('return '..stringValue)
  512. if not stringEval then
  513. error('Failed to scan number '..stringValue..' in JSON string at position '..startPos)
  514. end
  515. return stringEval(), endPos
  516. end
  517.  
  518. --- Scans a JSON object into a Lua object.
  519. -- startPos begins at the start of the object.
  520. -- Returns the object and the next starting position.
  521. -- @param state The state of JSON reader.
  522. -- @param startPos The starting position of the scan.
  523. -- @param traverse (optional) table with fields "path" and "callback" for traversing JSON
  524. -- @return table,int The scanned object as a table and the position of the next character to scan.
  525. function decode_scanObject(state, startPos, traverse)
  526. local object, elem_ready = not traverse and empty
  527. startPos = startPos + 1
  528. while true do
  529. repeat
  530. local curChar
  531. startPos, curChar = decode_scanWhitespace(state, startPos)
  532. if curChar == '}' then
  533. return object, startPos + 1
  534. elseif curChar == ',' then
  535. startPos = startPos + 1
  536. elem_ready = false
  537. end
  538. until curChar ~= ','
  539. if elem_ready then
  540. error('Comma is missing in JSON object at '..startPos)
  541. end
  542. -- Scan the key as string or unquoted identifier such as in {"a":1,b:2}
  543. local key, value
  544. key, startPos = decode(state, startPos, nil, true)
  545. local colon
  546. startPos, colon = decode_scanWhitespace(state, startPos)
  547. if colon ~= ':' then
  548. error('JSON object key-value assignment mal-formed at '..startPos)
  549. end
  550. startPos = decode_scanWhitespace(state, startPos + 1)
  551. if traverse then
  552. table_insert(traverse.path, key)
  553. end
  554. value, startPos = decode(state, startPos, traverse)
  555. if traverse then
  556. table_remove(traverse.path)
  557. else
  558. if object == empty then
  559. object = {}
  560. end
  561. object[key] = value
  562. end
  563. elem_ready = true
  564. end -- infinite loop while key-value pairs are found
  565. end
  566.  
  567. --- Scans JSON string for an identifier (unquoted key name inside object)
  568. -- Returns the string extracted as a Lua string, and the position after the closing quote.
  569. -- @param state The state of JSON reader.
  570. -- @param startPos The starting position of the scan.
  571. -- @return string,int The extracted string as a Lua string, and the next character to parse.
  572. function decode_scanIdentifier(state, startPos)
  573. local identifier, idx = match_with_pattern(state, startPos, '^[%w_%-%$]+', "read")
  574. if identifier == "" then
  575. error('JSON String decoding failed: missing key name at position '..startPos)
  576. end
  577. return identifier, idx
  578. end
  579.  
  580. -- START SoniEx2
  581. -- Initialize some things used by decode_scanString
  582. -- You know, for efficiency
  583. local escapeSequences = { t = "\t", f = "\f", r = "\r", n = "\n", b = "\b" }
  584. -- END SoniEx2
  585.  
  586. --- Scans a JSON string from the opening quote to the end of the string.
  587. -- Returns the string extracted as a Lua string, and the position after the closing quote.
  588. -- @param state The state of JSON reader.
  589. -- @param startPos The starting position of the scan.
  590. -- @return string,int The extracted string as a Lua string, and the next character to parse.
  591. function decode_scanString(state, startPos)
  592. local t, idx, surrogate_pair_started, regular_part = {}, startPos + 1
  593. while true do
  594. regular_part, idx = match_with_pattern(state, idx, '^[^"\\]+', "read")
  595. table_insert(t, regular_part)
  596. local w6 = get_word(state, idx, 6)
  597. local c = string_sub(w6, 1, 1)
  598. if c == '"' then
  599. return table_concat(t), idx + 1
  600. elseif c == '\\' then
  601. local esc = string_sub(w6, 2, 2)
  602. if esc == "u" then
  603. local n = tonumber(string_sub(w6, 3), 16)
  604. if not n then
  605. error("String decoding failed: bad Unicode escape "..w6.." at position "..idx)
  606. end
  607. -- Handling of UTF-16 surrogate pairs
  608. if n >= 0xD800 and n < 0xDC00 then
  609. surrogate_pair_started, n = n
  610. elseif n >= 0xDC00 and n < 0xE000 then
  611. n, surrogate_pair_started = surrogate_pair_started and (surrogate_pair_started - 0xD800) * 0x400 + (n - 0xDC00) + 0x10000
  612. end
  613. if n then
  614. -- Convert unicode codepoint n (0..0x10FFFF) to UTF-8 string
  615. local x
  616. if n < 0x80 then
  617. x = string_char(n % 0x80)
  618. elseif n < 0x800 then
  619. -- [110x xxxx] [10xx xxxx]
  620. x = string_char(0xC0 + (math_floor(n/64) % 0x20), 0x80 + (n % 0x40))
  621. elseif n < 0x10000 then
  622. -- [1110 xxxx] [10xx xxxx] [10xx xxxx]
  623. x = string_char(0xE0 + (math_floor(n/64/64) % 0x10), 0x80 + (math_floor(n/64) % 0x40), 0x80 + (n % 0x40))
  624. else
  625. -- [1111 0xxx] [10xx xxxx] [10xx xxxx] [10xx xxxx]
  626. x = string_char(0xF0 + (math_floor(n/64/64/64) % 8), 0x80 + (math_floor(n/64/64) % 0x40), 0x80 + (math_floor(n/64) % 0x40), 0x80 + (n % 0x40))
  627. end
  628. table_insert(t, x)
  629. end
  630. idx = idx + 6
  631. else
  632. table_insert(t, escapeSequences[esc] or esc)
  633. idx = idx + 2
  634. end
  635. else
  636. error('String decoding failed: missing closing " for string at position '..startPos)
  637. end
  638. end
  639. end
  640.  
  641. --- Scans a JSON string skipping all whitespace from the current start position.
  642. -- Returns the position of the first non-whitespace character.
  643. -- @param state The state of JSON reader.
  644. -- @param startPos The starting position where we should begin removing whitespace.
  645. -- @return int,char The first position where non-whitespace was encountered, non-whitespace char.
  646. function decode_scanWhitespace(state, startPos)
  647. while true do
  648. startPos = match_with_pattern(state, startPos, '^[ \n\r\t]+', "skip")
  649. local w2 = get_word(state, startPos, 2)
  650. if w2 == '/*' then
  651. local endPos = skip_until_word(state, startPos + 2, '*/')
  652. if not endPos then
  653. error("Unterminated comment in JSON string at "..startPos)
  654. end
  655. startPos = endPos
  656. else
  657. local next_char = string_sub(w2, 1, 1)
  658. if next_char == '' then
  659. error('Unexpected end of JSON')
  660. end
  661. return startPos, next_char
  662. end
  663. end
  664. end
  665.  
  666. --- Encodes a string to be JSON-compatible.
  667. -- This just involves backslash-escaping of quotes, slashes and control codes
  668. -- @param s The string to return as a JSON encoded (i.e. backquoted string)
  669. -- @return string The string appropriately escaped.
  670. local escapeList = {
  671. ['"'] = '\\"',
  672. ['\\'] = '\\\\',
  673. ['/'] = '\\/',
  674. ['\b'] = '\\b',
  675. ['\f'] = '\\f',
  676. ['\n'] = '\\n',
  677. ['\r'] = '\\r',
  678. ['\t'] = '\\t',
  679. ['\127'] = '\\u007F'
  680. }
  681. function encodeString(s)
  682. if type(s) == 'number' then
  683. s = math_type(s) == 'integer' and tostring(s) or string_format('%.f', s)
  684. end
  685. return string_gsub(s, ".", function(c) return escapeList[c] or c:byte() < 32 and string_format('\\u%04X', c:byte()) end)
  686. end
  687.  
  688. -- Determines whether the given Lua type is an array or a table / dictionary.
  689. -- We consider any table an array if it has indexes 1..n for its n items, and no other data in the table.
  690. -- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
  691. -- @param t The table to evaluate as an array
  692. -- @return boolean,number True if the table can be represented as an array, false otherwise.
  693. -- If true, the second returned value is the maximum number of indexed elements in the array.
  694. function isArray(t)
  695. -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
  696. -- (with the possible exception of 'n')
  697. local maxIndex = 0
  698. for k, v in pairs(t) do
  699. if type(k) == 'number' and math_floor(k) == k and 0 <= k and k <= 1e6 then -- k,v is an indexed pair
  700. if not isEncodable(v) then -- All array elements must be encodable
  701. return false
  702. end
  703. maxIndex = math_max(maxIndex, k)
  704. elseif not (k == 'n' and v == #t) then -- if it is n, then n does not hold the number of elements
  705. if isConvertibleToString(k) and isEncodable(v) then
  706. return false
  707. end
  708. end -- End of k,v not an indexed pair
  709. end -- End of loop across all pairs
  710. return true, maxIndex
  711. end
  712.  
  713. --- Determines whether the given Lua object / table / value can be JSON encoded.
  714. -- The only types that are JSON encodable are: string, boolean, number, nil, table and special tables json.null and json.empty.
  715. -- @param o The object to examine.
  716. -- @return boolean True if the object should be JSON encoded, false if it should be ignored.
  717. function isEncodable(o)
  718. local t = type(o)
  719. return t == 'string' or t == 'boolean' or t == 'number' and isRegularNumber(o) or t == 'nil' or t == 'table'
  720. end
  721.  
  722. --- Determines whether the given Lua object / table / variable can be a JSON key.
  723. -- Integer Lua numbers are allowed to be considered as valid string keys in JSON.
  724. -- @param o The object to examine.
  725. -- @return boolean True if the object can be converted to a string, false if it should be ignored.
  726. function isConvertibleToString(o)
  727. local t = type(o)
  728. return t == 'string' or t == 'number' and isRegularNumber(o) and (math_type(o) == 'integer' or math_floor(o) == o)
  729. end
  730.  
  731. local is_Inf_or_NaN = {[tostring(1/0)]=true, [tostring(-1/0)]=true, [tostring(0/0)]=true, [tostring(-(0/0))]=true}
  732. --- Determines whether the given Lua number is a regular number or Inf/Nan.
  733. -- @param v The number to examine.
  734. -- @return boolean True if the number is a regular number which may be encoded in JSON.
  735. function isRegularNumber(v)
  736. return not is_Inf_or_NaN[tostring(v)]
  737. end
  738.  
  739. end
  740.  
  741.  
  742. -- RSWarehouse.lua
  743. -- Author: Scott Adkins <[email protected]> (Zucanthor)
  744. -- Published: 2021-09-21
  745. --
  746. -- This program monitors work requests for the Minecolonies Warehouse and
  747. -- tries to fulfill requests from the Refined Storage network. If the
  748. -- RS network doesn't have enough items and a crafting pattern exists, a
  749. -- crafting job is scheduled to restock the items in order to fulfill the
  750. -- work request. The script will continuously loop, monitoring for new
  751. -- requests and checking on crafting jobs to fulfill previous requests.
  752.  
  753. -- The following is required for setup:
  754. -- * 1 ComputerCraft Computer
  755. -- * 1 or more ComputerCraft Monitors (recommend 3x3 monitors)
  756. -- * 1 Advanced Peripheral Colony Integrator
  757. -- * 1 Advanced Peripheral RS Bridge
  758. -- * 1 Chest or other storage container
  759. -- Attach an RS Cable from the RS network to the RS Bridge. Connect the
  760. -- storage container to the Minecolonies Warehouse Hut block. One idea is
  761. -- to set up a second RS network attached to the Warehouse Hut using an
  762. -- External Storage connector and then attach an Importer for that network
  763. -- to the storage container.
  764.  
  765. -- THINGS YOU CAN CUSTOMIZE IN THIS PROGRAM:
  766. -- Line 59: Specify the side storage container is at.
  767. -- Line 66: Name of log file for storing JSON data of all open requests.
  768. -- Lines 231+: Any items you find that should be manually provided.
  769. -- Line 373: Time in seconds between work order scans.
  770.  
  771. ----------------------------------------------------------------------------
  772. -- INITIALIZATION
  773. ----------------------------------------------------------------------------
  774.  
  775. -- Initialize Monitor
  776. -- A future update may allow for multiple monitors. This would allow one
  777. -- monitor to be used for logging and another to be used for work requests.
  778. local monitor = peripheral.find("monitor")
  779. if not monitor then error("Monitor not found.") end
  780. monitor.setTextScale(0.5)
  781. monitor.clear()
  782. monitor.setCursorPos(1, 1)
  783. monitor.setCursorBlink(false)
  784. print("Monitor initialized.")
  785.  
  786. -- Initialize RS Bridge
  787. local bridge = peripheral.find("meBridge")
  788. if not bridge then error("ME Bridge not found.") end
  789. print("RS Bridge initialized.")
  790.  
  791. -- Initialize Colony Integrator
  792. local colony = peripheral.find("colonyIntegrator")
  793. if not colony then error("Colony Integrator not found.") end
  794. if not colony.isInColony then error("Colony Integrator is not in a colony.") end
  795. print("Colony Integrator initialized.")
  796.  
  797. -- Point to location of chest or storage container
  798. -- A future update may autodetect where the storage container is and error
  799. -- out if no storage container is found.
  800. --local storage = peripheral.find("minecraft:chest")
  801. --if not storage then error("Storage not found") end
  802. storage = "up"
  803. print("Storage initialized.")
  804.  
  805. -- Name of log file to capture JSON data from the open requests. The log can
  806. -- be too big to edit within CC, which may require a "pastebin put" if you want
  807. -- to look at it. Logging could be improved to only capture Skipped items,
  808. -- which in turn will make log files smaller and edittable in CC directly.
  809. local logFile = "RSWarehouse.log"
  810.  
  811. ----------------------------------------------------------------------------
  812. -- FUNCTIONS
  813. ----------------------------------------------------------------------------
  814.  
  815. -- Prints to the screen one row after another, scrolling the screen when
  816. -- reaching the bottom. Acts as a normal display where text is printed in
  817. -- a standard way. Long lines are not wrapped and newlines are printed as
  818. -- spaces, both to be addressed in a future update.
  819. -- NOTE: No longer used in this program.
  820. function mPrintScrollable(mon, ...)
  821. w, h = mon.getSize()
  822. x, y = mon.getCursorPos()
  823.  
  824. -- Blink the cursor like a normal display.
  825. mon.setCursorBlink(true)
  826.  
  827. -- For multiple strings, append them with a space between each.
  828. for i = 2, #arg do t = t.." "..arg[i] end
  829. mon.write(arg[1])
  830. if y >= h then
  831. mon.scroll(1)
  832. mon.setCursorPos(1, y)
  833. else
  834. mon.setCursorPos(1, y+1)
  835. end
  836. end
  837.  
  838. -- Prints strings left, centered, or right justified at a specific row and
  839. -- specific foreground/background color.
  840. function mPrintRowJustified(mon, y, pos, text, ...)
  841. w, h = mon.getSize()
  842. fg = mon.getTextColor()
  843. bg = mon.getBackgroundColor()
  844.  
  845. if pos == "left" then x = 1 end
  846. if pos == "center" then x = math.floor((w - #text) / 2) end
  847. if pos == "right" then x = w - #text end
  848.  
  849. if #arg > 0 then mon.setTextColor(arg[1]) end
  850. if #arg > 1 then mon.setBackgroundColor(arg[2]) end
  851. mon.setCursorPos(x, y)
  852. mon.write(text)
  853. mon.setTextColor(fg)
  854. mon.setBackgroundColor(bg)
  855. end
  856.  
  857. -- Utility function that returns true if the provided character is a digit.
  858. -- Yes, this is a hack and there are better ways to do this. Clearly.
  859. function isdigit(c)
  860. if c == "0" then return true end
  861. if c == "1" then return true end
  862. if c == "2" then return true end
  863. if c == "3" then return true end
  864. if c == "4" then return true end
  865. if c == "5" then return true end
  866. if c == "6" then return true end
  867. if c == "7" then return true end
  868. if c == "8" then return true end
  869. if c == "9" then return true end
  870. return false
  871. end
  872.  
  873. -- Utility function that displays current time and remaining time on timer.
  874. -- For time of day, yellow is day, orange is sunset/sunrise, and red is night.
  875. -- The countdown timer is orange over 15s, yellow under 15s, and red under 5s.
  876. -- At night, the countdown timer is red and shows PAUSED insted of a time.
  877. function displayTimer(mon, t)
  878. now = os.time()
  879.  
  880. cycle = "day"
  881. cycle_color = colors.orange
  882. if now >= 4 and now < 6 then
  883. cycle = "sunrise"
  884. cycle_color = colors.orange
  885. elseif now >= 6 and now < 18 then
  886. cycle = "day"
  887. cycle_color = colors.yellow
  888. elseif now >= 18 and now < 19.5 then
  889. cycle = "sunset"
  890. cycle_color = colors.orange
  891. elseif now >= 19.5 or now < 5 then
  892. cycle = "night"
  893. cycle_color = colors.red
  894. end
  895.  
  896. timer_color = colors.orange
  897. if t < 15 then timer_color = colors.yellow end
  898. if t < 5 then timer_color = colors.red end
  899.  
  900. mPrintRowJustified(mon, 1, "left", string.format("Time: %s [%s] ", textutils.formatTime(now, false), cycle), cycle_color)
  901. if cycle ~= "night" then mPrintRowJustified(mon, 1, "right", string.format(" Remaining: %ss", t), timer_color)
  902. else mPrintRowJustified(mon, 1, "right", " Remaining: PAUSED", colors.red) end
  903. end
  904.  
  905. -- Scan all open work requests from the Warehouse and attempt to satisfy those
  906. -- requests. Display all activity on the monitor, including time of day and the
  907. -- countdown timer before next scan. This function is not called at night to
  908. -- save on some ticks, as the colonists are in bed anyways. Items in red mean
  909. -- work order can't be satisfied by Refined Storage (lack of pattern or lack of
  910. -- required crafting ingredients). Yellow means order partially filled and a
  911. -- crafting job was scheduled for the rest. Green means order fully filled.
  912. -- Blue means the Player needs to manually fill the work order. This includes
  913. -- equipment (Tools of Class), NBT items like armor, weapons and tools, as well
  914. -- as generic requests ike Compostables, Fuel, Food, Flowers, etc.
  915. function scanWorkRequests(mon, rs, chest)
  916. -- Before we do anything, prep the log file for this scan.
  917. -- The log file is truncated each time this function is called.
  918. file = fs.open(logFile, "w")
  919. print("\nScan starting at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  920.  
  921. -- We want to keep three different lists so that they can be
  922. -- displayed on the monitor in a more intelligent way. The first
  923. -- list is for the Builder requests. The second list is for the
  924. -- non-Builder requests. The third list is for any armor, tools
  925. -- and weapons requested by the colonists.
  926. builder_list = {}
  927. nonbuilder_list = {}
  928. equipment_list = {}
  929.  
  930. -- Scan RS for all items in its network. Ignore items with NBT data.
  931. -- If a Builder needs any items with NBT data, this function will need
  932. -- to be updated to not ignore those items.
  933. items = rs.listItems()
  934. item_array = {}
  935. for index, item in ipairs(items) do
  936. item_array[item.name] = {amount = item.amount, nbt = json.encode(item.nbt)}
  937. end
  938. end
  939.  
  940. -- Scan the Warehouse for all open work requests. For each item, try to
  941. -- provide as much as possible from RS, then craft whatever is needed
  942. -- after that. Green means item was provided entirely. Yellow means item
  943. -- is being crafted. Red means item is missing crafting recipe.
  944. workRequests = colony.getRequests()
  945. file.write(textutils.serialize(workRequests))
  946. for w in pairs(workRequests) do
  947. name = workRequests[w].name
  948. item = workRequests[w].items[1].name
  949. nbtt = workRequests[w].items[1].nbt
  950. target = workRequests[w].target
  951. desc = workRequests[w].desc
  952. needed = workRequests[w].count
  953. provided = 0
  954.  
  955. target_words = {}
  956. target_length = 0
  957. for word in target:gmatch("%S+") do
  958. table.insert(target_words, word)
  959. target_length = target_length + 1
  960. end
  961.  
  962. if target_length >= 3 then target_name = target_words[target_length-2] .. " " .. target_words[target_length]
  963. else target_name = target end
  964.  
  965. target_type = ""
  966. target_count = 1
  967. repeat
  968. if target_type ~= "" then target_type = target_type .. " " end
  969. target_type = target_type .. target_words[target_count]
  970. target_count = target_count + 1
  971. until target_count > target_length - 3
  972.  
  973. useRS = 1
  974. if string.find(desc, "Tool of class") then useRS = 0 end
  975. if string.find(name, "Hoe") then useRS = 0 end
  976. if string.find(name, "Shovel") then useRS = 0 end
  977. if string.find(name, "Axe") then useRS = 0 end
  978. if string.find(name, "Pickaxe") then useRS = 0 end
  979. if string.find(name, "Bow") then useRS = 0 end
  980. if string.find(name, "Sword") then useRS = 0 end
  981. if string.find(name, "Shield") then useRS = 0 end
  982. if string.find(name, "Helmet") then useRS = 0 end
  983. if string.find(name, "Leather Cap") then useRS = 0 end
  984. if string.find(name, "Chestplate") then useRS = 0 end
  985. if string.find(name, "Tunic") then useRS = 0 end
  986. if string.find(name, "Pants") then useRS = 0 end
  987. if string.find(name, "Leggings") then useRS = 0 end
  988. if string.find(name, "Boots") then useRS = 0 end
  989. if string.find(item, "domum_ornamentum") then useRS = 0 end
  990. if name == "Rallying Banner" then useRS = 0 end --bugged in alpha versions
  991. if name == "Crafter" then useRS = 0 end
  992. if name == "Compostable" then useRS = 0 end
  993. if name == "Fertilizer" then useRS = 0 end
  994. if name == "Flowers" then useRS = 0 end
  995. if name == "Food" then useRS = 0 end
  996. if name == "Fuel" then item = "minecraft:coal" end
  997. if name == "Smeltable Ore" then useRS = 0 end
  998. if name == "Stack List" then useRS = 0 end
  999.  
  1000. color = colors.blue
  1001. if useRS == 1 then
  1002. provided = 0
  1003. if item_array[item].amount > 0 then
  1004. provided = rs.exportItem({name=item, count=needed}, chest)
  1005. end
  1006.  
  1007. color = colors.green
  1008. if provided < needed then
  1009. if rs.isItemCrafting({name=item}) then
  1010. color = colors.yellow
  1011. print("[Crafting]", item)
  1012. else
  1013. if rs.craftItem({name=item, count=needed}) then
  1014. color = colors.yellow
  1015. print("[Scheduled]", needed, "x", item)
  1016. else
  1017. color = colors.red
  1018. print("[Failed]", item)
  1019. end
  1020. end
  1021. end
  1022. else
  1023. nameString = name .. " [" .. target .. "]"
  1024. print("[Skipped]", nameString)
  1025. end
  1026.  
  1027. if string.find(desc, "of class") then
  1028. level = "Any Level"
  1029. if string.find(desc, "with maximal level:Leather") then level = "Leather" end
  1030. if string.find(desc, "with maximal level:Gold") then level = "Gold" end
  1031. if string.find(desc, "with maximal level:Chain") then level = "Chain" end
  1032. if string.find(desc, "with maximal level:Wood or Gold") then level = "Wood or Gold" end
  1033. if string.find(desc, "with maximal level:Stone") then level = "Stone" end
  1034. if string.find(desc, "with maximal level:Iron") then level = "Iron" end
  1035. if string.find(desc, "with maximal level:Diamond") then level = "Diamond" end
  1036. new_name = level .. " " .. name
  1037. if level == "Any Level" then new_name = name .. " of any level" end
  1038. new_target = target_type .. " " .. target_name
  1039. equipment = { name=new_name, target=new_target, needed=needed, provided=provided, color=color}
  1040. table.insert(equipment_list, equipment)
  1041. elseif string.find(target, "Builder") then
  1042. builder = { name=name, item=item, target=target_name, needed=needed, provided=provided, color=color }
  1043. table.insert(builder_list, builder)
  1044. else
  1045. new_target = target_type .. " " .. target_name
  1046. if target_length < 3 then
  1047. new_target = target
  1048. end
  1049. nonbuilder = { name=name, target=new_target, needed=needed, provided=provided, color=color }
  1050. table.insert(nonbuilder_list, nonbuilder)
  1051. end
  1052. end
  1053.  
  1054. -- Show the various lists on the attached monitor.
  1055. row = 3
  1056. mon.clear()
  1057.  
  1058. header_shown = 0
  1059. for e in pairs(equipment_list) do
  1060. equipment = equipment_list[e]
  1061. if header_shown == 0 then
  1062. mPrintRowJustified(mon, row, "center", "Equipment")
  1063. header_shown = 1
  1064. row = row + 1
  1065. end
  1066. text = string.format("%d %s", equipment.needed, equipment.name)
  1067. mPrintRowJustified(mon, row, "left", text, equipment.color)
  1068. mPrintRowJustified(mon, row, "right", " " .. equipment.target, equipment.color)
  1069. row = row + 1
  1070. end
  1071.  
  1072. header_shown = 0
  1073. for b in pairs(builder_list) do
  1074. builder = builder_list[b]
  1075. if header_shown == 0 then
  1076. if row > 1 then row = row + 1 end
  1077. mPrintRowJustified(mon, row, "center", "Builder Requests")
  1078. header_shown = 1
  1079. row = row + 1
  1080. end
  1081. text = string.format("%d/%s", builder.provided, builder.name)
  1082. mPrintRowJustified(mon, row, "left", text, builder.color)
  1083. mPrintRowJustified(mon, row, "right", " " .. builder.target, builder.color)
  1084. row = row + 1
  1085. end
  1086.  
  1087. header_shown = 0
  1088. for n in pairs(nonbuilder_list) do
  1089. nonbuilder = nonbuilder_list[n]
  1090. if header_shown == 0 then
  1091. if row > 1 then row = row + 1 end
  1092. mPrintRowJustified(mon, row, "center", "Nonbuilder Requests")
  1093. header_shown = 1
  1094. row = row + 1
  1095. end
  1096. text = string.format("%d %s", nonbuilder.needed, nonbuilder.name)
  1097. if isdigit(nonbuilder.name:sub(1,1)) then
  1098. text = string.format("%d/%s", nonbuilder.provided, nonbuilder.name)
  1099. end
  1100. mPrintRowJustified(mon, row, "left", text, nonbuilder.color)
  1101. mPrintRowJustified(mon, row, "right", " " .. nonbuilder.target, nonbuilder.color)
  1102. row = row + 1
  1103. end
  1104.  
  1105. if row == 3 then mPrintRowJustified(mon, row, "center", "No Open Requests") end
  1106. print("Scan completed at", textutils.formatTime(os.time(), false) .. " (" .. os.time() ..").")
  1107. file.close()
  1108. end
  1109.  
  1110. ----------------------------------------------------------------------------
  1111. -- MAIN
  1112. ----------------------------------------------------------------------------
  1113.  
  1114. -- Scan for requests periodically. This will catch any updates that were
  1115. -- triggered from the previous scan. Right-clicking on the monitor will
  1116. -- trigger an immediate scan and reset the timer. Unfortunately, there is
  1117. -- no way to capture left-clicks on the monitor.
  1118. local time_between_runs = 30
  1119. local current_run = time_between_runs
  1120. scanWorkRequests(monitor, bridge, storage)
  1121. displayTimer(monitor, current_run)
  1122. local TIMER = os.startTimer(1)
  1123.  
  1124. while true do
  1125. local e = {os.pullEvent()}
  1126. if e[1] == "timer" and e[2] == TIMER then
  1127. now = os.time()
  1128. if now >= 5 and now < 19.5 then
  1129. current_run = current_run - 1
  1130. if current_run <= 0 then
  1131. scanWorkRequests(monitor, bridge, storage)
  1132. current_run = time_between_runs
  1133. end
  1134. end
  1135. displayTimer(monitor, current_run)
  1136. TIMER = os.startTimer(1)
  1137. elseif e[1] == "monitor_touch" then
  1138. os.cancelTimer(TIMER)
  1139. scanWorkRequests(monitor, bridge, storage)
  1140. current_run = time_between_runs
  1141. displayTimer(monitor, current_run)
  1142. TIMER = os.startTimer(1)
  1143. end
  1144. end
Advertisement
Add Comment
Please, Sign In to add comment