M CMakeLists.txt => CMakeLists.txt +7 -0
@@ 29,6 29,7 @@ include(AddPackage)
include(AutoModuleOption)
include(AddDirectories)
include(AddDatabases)
+include(AddScripts)
message("Selected product: ${PRODUCT}")
message("Selected board: ${BOARD}")
@@ 192,6 193,12 @@ add_databases_target(
DEVEL ${WITH_DEVELOPMENT_FEATURES}
DEPENDS create_databases_common
)
+# Install scripts
+add_scripts_target(
+ TARGET install_scripts
+ PRODUCT ${PRODUCT}
+ DEST_DIR ${CMAKE_BINARY_DIR}/sysroot/system_a/scripts
+)
add_library(version-header INTERFACE)
target_include_directories(version-header INTERFACE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/source/include>)
A cmake/modules/AddScripts.cmake => cmake/modules/AddScripts.cmake +18 -0
@@ 0,0 1,18 @@
+function(add_scripts_target)
+ cmake_parse_arguments(
+ _ARG
+ ""
+ "TARGET;DEST_DIR;PRODUCT"
+ "DEPENDS"
+ ${ARGN}
+ )
+
+ add_custom_target(
+ ${_ARG_TARGET}
+ DEPENDS ${_ARG_DEPENDS}
+
+ COMMAND ${PROJECT_SOURCE_DIR}/scripts/lua/install.sh ${_ARG_PRODUCT} ${_ARG_DEST_DIR}
+ COMMENT
+ "Installing scripts for ${_ARG_PRODUCT} to ${_ARG_DEST_DIR} directory"
+ )
+endfunction()<
\ No newline at end of file
M products/BellHybrid/CMakeLists.txt => products/BellHybrid/CMakeLists.txt +2 -0
@@ 96,6 96,7 @@ if (${PROJECT_TARGET} STREQUAL "TARGET_RT1051")
SYSROOT sysroot
LUTS Luts.bin
DEPENDS
+ install_scripts
create_product_databases
create_user_directories
assets
@@ 111,6 112,7 @@ else()
SYSROOT sysroot
LUTS ""
DEPENDS
+ install_scripts
create_product_databases
create_user_directories
assets
M products/PurePhone/CMakeLists.txt => products/PurePhone/CMakeLists.txt +2 -0
@@ 121,6 121,7 @@ if (${PROJECT_TARGET} STREQUAL "TARGET_RT1051")
SYSROOT sysroot
LUTS Luts.bin
DEPENDS
+ install_scripts
create_product_databases
create_user_directories
assets updater.bin-target
@@ 135,6 136,7 @@ else()
SYSROOT sysroot
LUTS ""
DEPENDS
+ install_scripts
create_product_databases
create_user_directories
assets
A scripts/lua/assets/BellHybrid/gui_image_factory_reset_failed.bin => scripts/lua/assets/BellHybrid/gui_image_factory_reset_failed.bin +0 -0
A scripts/lua/assets/BellHybrid/gui_image_factory_reset_in_progress.bin => scripts/lua/assets/BellHybrid/gui_image_factory_reset_in_progress.bin +0 -0
A scripts/lua/assets/BellHybrid/gui_image_factory_reset_success.bin => scripts/lua/assets/BellHybrid/gui_image_factory_reset_success.bin +0 -0
A scripts/lua/assets/BellHybrid/gui_image_update_failed.bin => scripts/lua/assets/BellHybrid/gui_image_update_failed.bin +0 -0
A scripts/lua/assets/BellHybrid/gui_image_update_in_progress.bin => scripts/lua/assets/BellHybrid/gui_image_update_in_progress.bin +0 -0
A scripts/lua/assets/BellHybrid/gui_image_update_success.bin => scripts/lua/assets/BellHybrid/gui_image_update_success.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_backup_failed.bin => scripts/lua/assets/PurePhone/gui_image_backup_failed.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_backup_in_progress.bin => scripts/lua/assets/PurePhone/gui_image_backup_in_progress.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_backup_success.bin => scripts/lua/assets/PurePhone/gui_image_backup_success.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_factory_reset_failed.bin => scripts/lua/assets/PurePhone/gui_image_factory_reset_failed.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_factory_reset_in_progress.bin => scripts/lua/assets/PurePhone/gui_image_factory_reset_in_progress.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_factory_reset_success.bin => scripts/lua/assets/PurePhone/gui_image_factory_reset_success.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_keys_failed.bin => scripts/lua/assets/PurePhone/gui_image_keys_failed.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_keys_in_progress.bin => scripts/lua/assets/PurePhone/gui_image_keys_in_progress.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_keys_success.bin => scripts/lua/assets/PurePhone/gui_image_keys_success.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_recovery_failed.bin => scripts/lua/assets/PurePhone/gui_image_recovery_failed.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_recovery_in_progress.bin => scripts/lua/assets/PurePhone/gui_image_recovery_in_progress.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_recovery_success.bin => scripts/lua/assets/PurePhone/gui_image_recovery_success.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_restore_failed.bin => scripts/lua/assets/PurePhone/gui_image_restore_failed.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_restore_in_progress.bin => scripts/lua/assets/PurePhone/gui_image_restore_in_progress.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_restore_success.bin => scripts/lua/assets/PurePhone/gui_image_restore_success.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_update_failed.bin => scripts/lua/assets/PurePhone/gui_image_update_failed.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_update_in_progress.bin => scripts/lua/assets/PurePhone/gui_image_update_in_progress.bin +0 -0
A scripts/lua/assets/PurePhone/gui_image_update_success.bin => scripts/lua/assets/PurePhone/gui_image_update_success.bin +0 -0
A scripts/lua/backup.lua => scripts/lua/backup.lua +16 -0
@@ 0,0 1,16 @@
+local helpers = require('helpers')
+local recovery = require('recovery')
+local tar = require('ltar')
+local lfs = require('lfs')
+local backup = {}
+
+backup.script_name = "backup.lua"
+backup.img_in_progress = "assets/gui_image_backup_in_progress.bin"
+backup.img_success = "assets/gui_image_backup_success.bin"
+backup.img_failure = "assets/gui_image_backup_failed.bin"
+
+function backup.execute()
+end
+
+return backup
+
A scripts/lua/entry.lua => scripts/lua/entry.lua +86 -0
@@ 0,0 1,86 @@
+package.path = package.path .. ";share/?.lua"
+
+local helpers = require('helpers')
+
+local rec = require('recovery')
+local backup = require('backup')
+local restore = require('restore')
+local update = require('update')
+local factory = require('factory')
+
+local scripts = {}
+scripts[rec.sys.boot_reason_codes.backup] = backup
+scripts[rec.sys.boot_reason_codes.restore] = restore
+scripts[rec.sys.boot_reason_codes.update] = update
+scripts[rec.sys.boot_reason_codes.factory] = factory
+
+--- Display image
+-- @function display_image
+-- @param path path to raw image data
+local function display_image(path)
+ local raw_data = helpers.read_whole_file(path)
+ rec.gui.clear()
+ rec.gui.display_raw_img(600, 480, raw_data)
+end
+
+local function print_recovery_info()
+ print(string.format("PureRecovery version: %s, branch: %s, revision: %s", rec.version(), rec.branch(),
+ rec.revision()))
+end
+
+local function print_boot_reason()
+ print(string.format("Boot reason: %s", rec.sys.boot_reason_str()))
+end
+
+local function generate_report_file(boot_reason, success, message)
+ local file_path = rec.sys.source_slot() .. "/log/recovery_status.json"
+ local body = string.format(
+ "{\"version\": \"%s\",\"branch\": \"%s\",\"revision\": \"%s\",\"boot_reason\": %d,\"result\": %s,\"message\": \"%s\"}",
+ rec.version(), rec.branch(), rec.revision(), boot_reason, tostring(success), message)
+ local fd = io.open(file_path, 'w')
+ fd:write(body)
+ fd:close()
+end
+
+local function handle_boot_reason()
+ local boot_reason = rec.sys.boot_reason()
+ rec.sys.set_boot_reason(rec.sys.boot_reason_codes.os)
+ return boot_reason
+end
+
+local function handle_script_success(boot_reason)
+ display_image(scripts[boot_reason].img_success)
+ print("Finished successfully")
+end
+
+local function handle_script_failure(boot_reason, message)
+ print(message)
+ display_image(scripts[boot_reason].img_failure)
+end
+
+local function handle_exit(boot_reason, status, message)
+ generate_report_file(boot_reason, status, message)
+ rec.sys.sleep(1)
+ rec.gui.clear()
+end
+
+local function invoke_script(boot_reason)
+ print(string.format("Executing '%s' script...", scripts[boot_reason].script_name))
+ display_image(scripts[boot_reason].img_in_progress)
+
+ local status, message = pcall(scripts[boot_reason].execute)
+ if status then
+ handle_script_success(boot_reason)
+ return true, "ok"
+ else
+ handle_script_failure(boot_reason, message)
+ return false, message
+ end
+end
+
+print_recovery_info()
+print_boot_reason()
+local boot_reason = handle_boot_reason()
+local status, message = invoke_script(boot_reason)
+handle_exit(boot_reason, status, message)
+
A scripts/lua/factory.lua => scripts/lua/factory.lua +39 -0
@@ 0,0 1,39 @@
+local helpers = require('helpers')
+local recovery = require('recovery')
+
+local factory = {}
+local user_dir = recovery.sys.user()
+local system_dir = recovery.sys.source_slot()
+local db_dir = system_dir .. "/db"
+local db_factory_dir = system_dir .. "/db/factory"
+local file_indexer_cache = system_dir .. "/data/.directory_is_indexed"
+
+factory.script_name = "factory.lua"
+factory.img_in_progress = "assets/gui_image_factory_reset_in_progress.bin"
+factory.img_success = "assets/gui_image_factory_reset_success.bin"
+factory.img_failure = "assets/gui_image_factory_reset_failed.bin"
+
+local function remove_old_databases()
+ print(string.format("Removing old databases from '%s'", db_dir))
+ helpers.rm_files_from_dir(db_dir)
+end
+
+local function copy_clean_databases()
+ print(string.format("Copying clean databases from '%s' to '%s'", db_factory_dir, db_dir))
+ helpers.copy_dir(db_factory_dir, db_dir)
+end
+
+local function remove_cache()
+ if helpers.exists(file_indexer_cache) then
+ print(string.format("Removing cache file '%s'", file_indexer_cache))
+ assert(os.remove(file_indexer_cache))
+ end
+end
+
+function factory.execute()
+ remove_old_databases()
+ copy_clean_databases()
+ remove_cache()
+end
+
+return factory
A scripts/lua/install.sh => scripts/lua/install.sh +36 -0
@@ 0,0 1,36 @@
+#!/usr/bin/env bash
+# Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+function validate_product_selection() {
+ case ${PRODUCT,,} in
+ pure | purephone)
+ PRODUCT_SHORTNAME="pure"
+ PRODUCT="PurePhone"
+ return 0 ;;
+ bell | bellhybrid)
+ PRODUCT_SHORTNAME="bell"
+ PRODUCT="BellHybrid"
+ return 0 ;;
+ *)
+ echo "Wrong product: \"${PRODUCT}\""
+ esac
+}
+
+PRODUCT=$1
+DESTINATION=$2
+
+if validate_product_selection; then
+ if [ -d ${DESTINATION} ]; then
+ rm -Rf ${DESTINATION}
+ fi
+ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
+ cd "$parent_path"
+
+ mkdir -p ${DESTINATION}
+ cp -r assets/${PRODUCT} ${DESTINATION}/assets/
+ cp -r share ${DESTINATION}/share
+ cp -r migration/migration.lua ${DESTINATION}/share
+ cp *.lua ${DESTINATION}
+ cd -
+fi<
\ No newline at end of file
A scripts/lua/restore.lua => scripts/lua/restore.lua +17 -0
@@ 0,0 1,17 @@
+local helpers = require('helpers')
+local recovery = require('recovery')
+local ltar = require('ltar')
+local lfs = require('lfs')
+local json = require('lunajson')
+local migration = require('migration')
+local restore = {}
+
+restore.script_name = "restore.lua"
+restore.img_in_progress = "assets/gui_image_restore_in_progress.bin"
+restore.img_success = "assets/gui_image_restore_success.bin"
+restore.img_failure = "assets/gui_image_restore_failed.bin"
+
+function restore.execute()
+end
+
+return restore
A scripts/lua/share/helpers.lua => scripts/lua/share/helpers.lua +202 -0
@@ 0,0 1,202 @@
+-- Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+--- helper scripts
+-- @module helpers
+local lfs = require('lfs')
+
+local helpers = {}
+
+local function dirtree(dir)
+ assert(dir and dir ~= "", "directory parameter is missing or empty")
+ if string.sub(dir, -1) == "/" then
+ dir = string.sub(dir, 1, -2)
+ end
+
+ local function yieldtree(dir)
+ for entry in lfs.dir(dir) do
+ if entry ~= "." and entry ~= ".." then
+ entry = dir .. "/" .. entry
+ local attr = lfs.attributes(entry)
+ coroutine.yield(entry, attr)
+ if attr.mode == "directory" then
+ yieldtree(entry)
+ end
+ end
+ end
+ end
+
+ return coroutine.wrap(function()
+ yieldtree(dir)
+ end)
+end
+
+local function ends_with(str, ending)
+ return ending == "" or str:sub(-#ending) == ending
+end
+
+local function starts_with(str, start)
+ return str:sub(1, #start) == start
+end
+
+local function build_path(prefix, name)
+ if not ends_with(prefix, '/') then
+ prefix = prefix .. '/'
+ end
+ if starts_with(name, '/') then
+ name = name:sub(2, -1)
+ end
+ return prefix .. name
+end
+
+local function strip_from_prefix(prefix,path)
+ local name = path:gsub(prefix, "")
+ name = name:sub(2, -1)
+ return name
+end
+
+local function basedir(p)
+ return p:gsub('[^\\/]+[\\/]?$', '')
+end
+
+--- Copy file
+-- @function copy_file
+-- @param filename_in input file
+-- @param filename_out output file
+function helpers.copy_file(filename_in, filename_out)
+ local size = 1024 * 512
+ local size_in = lfs.attributes(filename_in, "size")
+ local fd_in = assert(io.open(filename_in, "r"))
+ local fd_out = assert(io.open(filename_out, "w"))
+ while true do
+ local block = fd_in:read(size)
+ if not block then
+ break
+ end
+ assert(fd_out:write(block))
+ end
+ fd_in:close()
+ fd_out:close()
+end
+
+--- Remove directory and its contents
+-- @function rmdir
+-- @param dir directory to remove
+function helpers.rmdir(dir)
+ for file in lfs.dir(dir) do
+ local file_path = dir .. "/" .. file
+ if file ~= "." and file ~= ".." then
+ if lfs.attributes(file_path, "mode") == "file" then
+ assert(os.remove(file_path))
+ elseif lfs.attributes(file_path, "mode") == "directory" then
+ helpers.rmdir(file_path)
+ end
+ end
+ end
+ lfs.rmdir(dir)
+end
+
+--- Remove all files from directory without touching internal directories
+-- @function rm_files_from_dir
+-- @param dir directory to remove files from
+function helpers.rm_files_from_dir(dir)
+ for file in lfs.dir(dir) do
+ local file_path = dir .. "/" .. file
+ if file ~= "." and file ~= ".." then
+ if lfs.attributes(file_path, "mode") == "file" then
+ assert(os.remove(file_path))
+ end
+ end
+ end
+end
+
+--- Copy directory recursively
+-- @function copy_dir
+-- @param from source directory
+-- @param where target directory
+function helpers.copy_dir(from, where)
+ for filename, attr in dirtree(from) do
+ local name = filename:gsub(from, "")
+ if attr.mode == "directory" then
+ assert(lfs.mkdir(build_path(where, name)))
+ else
+ helpers.copy_file(filename, build_path(where, name))
+ end
+ end
+end
+
+--- Get the size of specified directory using regex filter
+-- @function dir_size_filtered
+-- @param path directory path
+-- @param matcher regex expression
+-- @return total size of directory in bytes
+function helpers.dir_size_filtered(path, filter)
+ local total_size = 0
+ for filename, attr in dirtree(path) do
+ local name = strip_from_prefix(path,filename)
+ if name:match(filter) then
+ total_size = total_size + attr.size
+ end
+ end
+ return total_size
+end
+
+--- Get the size of specified directory
+-- @function dir_size
+-- @param path directory path
+-- @return total size of directory in bytes
+function helpers.dir_size(path)
+ return helpers.dir_size_regex('.*')
+end
+
+--- Check if specified dir/file exists
+-- @function exists
+-- @param path directory or file path
+-- @return true or false
+function helpers.exists(path)
+ local ret = lfs.attributes(path)
+ return ret ~= nil
+end
+
+--- Read the whole file at once
+-- @function read_whole_file
+-- @param file file path
+-- @return file contents
+function helpers.read_whole_file(file)
+ local f = assert(io.open(file, "rb"))
+ local content = assert(f:read("*a"))
+ f:close()
+ return content
+end
+
+--- Extract filename from filename path
+-- @function get_filename
+-- @param file file path
+-- @return filename
+function helpers.get_filename(file)
+ return file:match("^.+/(.+)$")
+end
+
+--- Extract file extension from filename
+-- @function get_file_extension
+-- @param file file path
+-- @return file extension
+function helpers.get_file_extension(file)
+ return file:match("^.+(%..+)$")
+end
+
+function helpers.mkdirp(p)
+ if lfs.attributes(p, 'mode') == 'directory' then
+ return nil, 'already exists'
+ end
+
+ local b = basedir(p)
+ if #b > 0 and lfs.attributes(b, 'mode') ~= 'directory' then
+ local r, m = helpers.mkdirp(b)
+ if not r then
+ return r, m
+ end
+ end
+ return lfs.mkdir(p)
+end
+
+return helpers
A scripts/lua/share/ltar.lua => scripts/lua/share/ltar.lua +233 -0
@@ 0,0 1,233 @@
+--- LUA support for TAR packages
+-- @module ltar
+local microtar = require("lmicrotar")
+local lfs = require("lfs")
+
+local tar = {}
+
+local function strip_from_prefix(prefix, filename)
+ local name = filename:gsub(prefix, "")
+ name = name:sub(2, -1)
+ return name
+end
+
+local function get_filename(file)
+ return file:match("^.+/(.+)$")
+end
+
+local function basedir(p)
+ return p:gsub('[^\\/]+[\\/]?$', '')
+end
+
+local function mkdirp(p)
+ if lfs.attributes(p, 'mode') == 'directory' then
+ return nil, 'already exists'
+ end
+
+ local b = basedir(p)
+ if #b > 0 and lfs.attributes(b, 'mode') ~= 'directory' then
+ local r, m = mkdirp(b)
+ if not r then
+ return r, m
+ end
+ end
+ return lfs.mkdir(p)
+end
+
+local function dirtree(dir)
+ assert(dir and dir ~= "", "directory parameter is missing or empty")
+ if string.sub(dir, -1) == "/" then
+ dir = string.sub(dir, 1, -2)
+ end
+
+ local function yieldtree(dir)
+ for entry in lfs.dir(dir) do
+ if entry ~= "." and entry ~= ".." then
+ entry = dir .. "/" .. entry
+ local attr = lfs.attributes(entry)
+ coroutine.yield(entry, attr)
+ if attr.mode == "directory" then
+ yieldtree(entry)
+ end
+ end
+ end
+ end
+
+ return coroutine.wrap(function()
+ yieldtree(dir)
+ end)
+end
+
+local function write_tarfile_chunks(handle, fd)
+ local size = 1024 * 512
+ while true do
+ local block = fd:read(size)
+ if not block then
+ break
+ end
+ handle:write_data(block, block:len())
+ end
+end
+
+local function read_tarfile_chunks(handle, fd, total_size)
+ local block_size = 1024 * 512
+ local to_read = {};
+
+ while total_size > 0 do
+ if total_size > block_size then
+ to_read = block_size
+ else
+ to_read = total_size
+ end
+ fd:write(handle:read_data(to_read))
+ total_size = total_size - to_read
+ end
+end
+
+--- Create empty tar file
+-- @function create
+-- @param path where to put newly created tar file
+-- @return tar handle or nil
+function tar.create(path)
+ Handle = {
+ handle = microtar.open(path, "w"),
+ add_file = function(self, filename)
+ local size = lfs.attributes(filename, "size")
+ local fd = io.open(filename, "r")
+ self.handle:write_file_header(filename, size)
+ write_tarfile_chunks(self.handle, fd)
+ fd:close()
+ end,
+ add_directory = function(self, path)
+ self.handle:write_dir_header(path)
+ end,
+ close = function(self)
+ self.handle:close()
+ end
+ }
+ return Handle
+end
+
+--- Iterate over contents of tar file
+-- @function iter_by_handle
+-- @param handle tar handle create by @{create}
+function tar.iter_by_handle(handle)
+ return function()
+ local f = handle:read_header()
+ if f then
+ handle:next()
+ return f
+ end
+ end
+end
+
+--- Iterate over contents of tar file
+-- @function iter_by_path
+-- @param path path to tar file
+function tar.iter_by_path(path)
+ local handle = microtar.open(path)
+ return function()
+ local f = handle:read_header()
+ if f then
+ handle:next()
+ return f
+ end
+ end
+end
+
+--- Pack contents of specified dir to tar file
+-- @function create_from_path
+-- @param path directory
+-- @param where where to save tar file
+function tar.create_from_path(path, where)
+ tar.create_from_path_match(path, where, ".*")
+end
+
+--- Pack contents of specified dir to tar file using regex
+-- @function create_from_path_regex
+-- @param path directory
+-- @param where where to save tar file
+-- @param matcher regex expression
+function tar.create_from_path_regex(path, where, matcher)
+ local handle = microtar.open(where, "w")
+ for filename, attr in dirtree(path) do
+ local name = strip_from_prefix(path, filename)
+ if name:match(matcher) then
+ if attr.mode == "directory" then
+ handle:write_dir_header(name)
+ else
+ handle:write_file_header(name, attr.size)
+ local fd = io.open(filename, "r")
+ write_tarfile_chunks(handle, fd)
+ fd:close()
+ end
+ end
+ end
+ handle:close()
+end
+
+--- Unpack tar file to specified directory
+-- @function unpack
+-- @param path directory
+-- @param where where to store unpacked files
+function tar.unpack(path, where)
+ local handle = microtar.open(path)
+ local header = handle:read_header()
+ while header do
+ if header.type == microtar.TDIR then
+ mkdirp(where .. "/" .. header.name)
+ elseif header.type == microtar.TREG then
+ local fd = io.open(where .. "/" .. header.name, "w")
+ read_tarfile_chunks(handle, fd, header.size)
+ fd:close()
+ end
+ handle:next()
+ header = handle:read_header()
+ end
+ handle:close()
+end
+
+--- Append directory to already existing tar file
+-- @function append
+-- @param path directory
+-- @param where location of already existing tar file
+function tar.append(path, where)
+ local handle = microtar.open(where, "a")
+ for filename, attr in dirtree(path) do
+ if attr.mode == "directory" then
+ handle:write_dir_header(filename)
+ else
+ handle:write_file_header(filename, attr.size)
+ local fd = io.open(filename, "r")
+ write_tarfile_chunks(handle, fd)
+ fd:close()
+ end
+ end
+ handle:close()
+end
+
+--- Append file to already existing tar file
+-- @function append_file
+-- @param path file path
+-- @param where location of already existing tar file
+function tar.append_file(path, where)
+ local filename = get_filename(path)
+ local size = lfs.attributes(path, 'size')
+ local handle = microtar.open(where, "a")
+ handle:write_file_header(filename, size)
+ local fd = io.open(path, "r")
+ write_tarfile_chunks(handle, fd)
+ fd:close()
+ handle:close()
+end
+
+function tar.find(tar_path, what)
+ local handle = microtar.open(tar_path)
+ local stats = handle:find(what)
+ if stats ~= nil then
+ return handle:read_data()
+ end
+ return nil
+end
+
+return tar
A scripts/lua/share/lunajson.lua => scripts/lua/share/lunajson.lua +11 -0
@@ 0,0 1,11 @@
+local newdecoder = require 'lunajson.decoder'
+local newencoder = require 'lunajson.encoder'
+local sax = require 'lunajson.sax'
+-- If you need multiple contexts of decoder and/or encoder,
+-- you can require lunajson.decoder and/or lunajson.encoder directly.
+return {
+ decode = newdecoder(),
+ encode = newencoder(),
+ newparser = sax.newparser,
+ newfileparser = sax.newfileparser,
+}
A scripts/lua/share/lunajson/decoder.lua => scripts/lua/share/lunajson/decoder.lua +515 -0
@@ 0,0 1,515 @@
+local setmetatable, tonumber, tostring =
+ setmetatable, tonumber, tostring
+local floor, inf =
+ math.floor, math.huge
+local mininteger, tointeger =
+ math.mininteger or nil, math.tointeger or nil
+local byte, char, find, gsub, match, sub =
+ string.byte, string.char, string.find, string.gsub, string.match, string.sub
+
+local function _decode_error(pos, errmsg)
+ error("parse error at " .. pos .. ": " .. errmsg, 2)
+end
+
+local f_str_ctrl_pat
+if _VERSION == "Lua 5.1" then
+ -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
+ f_str_ctrl_pat = '[^\32-\255]'
+else
+ f_str_ctrl_pat = '[\0-\31]'
+end
+
+local _ENV = nil
+
+
+local function newdecoder()
+ local json, pos, nullv, arraylen, rec_depth
+
+ -- `f` is the temporary for dispatcher[c] and
+ -- the dummy for the first return value of `find`
+ local dispatcher, f
+
+ --[[
+ Helper
+ --]]
+ local function decode_error(errmsg)
+ return _decode_error(pos, errmsg)
+ end
+
+ --[[
+ Invalid
+ --]]
+ local function f_err()
+ decode_error('invalid value')
+ end
+
+ --[[
+ Constants
+ --]]
+ -- null
+ local function f_nul()
+ if sub(json, pos, pos+2) == 'ull' then
+ pos = pos+3
+ return nullv
+ end
+ decode_error('invalid value')
+ end
+
+ -- false
+ local function f_fls()
+ if sub(json, pos, pos+3) == 'alse' then
+ pos = pos+4
+ return false
+ end
+ decode_error('invalid value')
+ end
+
+ -- true
+ local function f_tru()
+ if sub(json, pos, pos+2) == 'rue' then
+ pos = pos+3
+ return true
+ end
+ decode_error('invalid value')
+ end
+
+ --[[
+ Numbers
+ Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp)
+ is captured as a number and its conformance to the JSON spec is checked.
+ --]]
+ -- deal with non-standard locales
+ local radixmark = match(tostring(0.5), '[^0-9]')
+ local fixedtonumber = tonumber
+ if radixmark ~= '.' then
+ if find(radixmark, '%W') then
+ radixmark = '%' .. radixmark
+ end
+ fixedtonumber = function(s)
+ return tonumber(gsub(s, '.', radixmark))
+ end
+ end
+
+ local function number_error()
+ return decode_error('invalid number')
+ end
+
+ -- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?`
+ local function f_zro(mns)
+ local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0
+
+ if num == '' then
+ if c == '' then
+ if mns then
+ return -0.0
+ end
+ return 0
+ end
+
+ if c == 'e' or c == 'E' then
+ num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
+ if c == '' then
+ pos = pos + #num
+ if mns then
+ return -0.0
+ end
+ return 0.0
+ end
+ end
+ number_error()
+ end
+
+ if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then
+ number_error()
+ end
+
+ if c ~= '' then
+ if c == 'e' or c == 'E' then
+ num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
+ end
+ if c ~= '' then
+ number_error()
+ end
+ end
+
+ pos = pos + #num
+ c = fixedtonumber(num)
+
+ if mns then
+ c = -c
+ end
+ return c
+ end
+
+ -- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?`
+ local function f_num(mns)
+ pos = pos-1
+ local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos)
+ if byte(num, -1) == 0x2E then -- error if ended with period
+ number_error()
+ end
+
+ if c ~= '' then
+ if c ~= 'e' and c ~= 'E' then
+ number_error()
+ end
+ num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
+ if not num or c ~= '' then
+ number_error()
+ end
+ end
+
+ pos = pos + #num
+ c = fixedtonumber(num)
+
+ if mns then
+ c = -c
+ if c == mininteger and not find(num, '[^0-9]') then
+ c = mininteger
+ end
+ end
+ return c
+ end
+
+ -- skip minus sign
+ local function f_mns()
+ local c = byte(json, pos)
+ if c then
+ pos = pos+1
+ if c > 0x30 then
+ if c < 0x3A then
+ return f_num(true)
+ end
+ else
+ if c > 0x2F then
+ return f_zro(true)
+ end
+ end
+ end
+ decode_error('invalid number')
+ end
+
+ --[[
+ Strings
+ --]]
+ local f_str_hextbl = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+ 0x8, 0x9, inf, inf, inf, inf, inf, inf,
+ inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf,
+ inf, inf, inf, inf, inf, inf, inf, inf,
+ inf, inf, inf, inf, inf, inf, inf, inf,
+ inf, inf, inf, inf, inf, inf, inf, inf,
+ inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+ __index = function()
+ return inf
+ end
+ }
+ setmetatable(f_str_hextbl, f_str_hextbl)
+
+ local f_str_escapetbl = {
+ ['"'] = '"',
+ ['\\'] = '\\',
+ ['/'] = '/',
+ ['b'] = '\b',
+ ['f'] = '\f',
+ ['n'] = '\n',
+ ['r'] = '\r',
+ ['t'] = '\t',
+ __index = function()
+ decode_error("invalid escape sequence")
+ end
+ }
+ setmetatable(f_str_escapetbl, f_str_escapetbl)
+
+ local function surrogate_first_error()
+ return decode_error("1st surrogate pair byte not continued by 2nd")
+ end
+
+ local f_str_surrogate_prev = 0
+ local function f_str_subst(ch, ucode)
+ if ch == 'u' then
+ local c1, c2, c3, c4, rest = byte(ucode, 1, 5)
+ ucode = f_str_hextbl[c1-47] * 0x1000 +
+ f_str_hextbl[c2-47] * 0x100 +
+ f_str_hextbl[c3-47] * 0x10 +
+ f_str_hextbl[c4-47]
+ if ucode ~= inf then
+ if ucode < 0x80 then -- 1byte
+ if rest then
+ return char(ucode, rest)
+ end
+ return char(ucode)
+ elseif ucode < 0x800 then -- 2bytes
+ c1 = floor(ucode / 0x40)
+ c2 = ucode - c1 * 0x40
+ c1 = c1 + 0xC0
+ c2 = c2 + 0x80
+ if rest then
+ return char(c1, c2, rest)
+ end
+ return char(c1, c2)
+ elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes
+ c1 = floor(ucode / 0x1000)
+ ucode = ucode - c1 * 0x1000
+ c2 = floor(ucode / 0x40)
+ c3 = ucode - c2 * 0x40
+ c1 = c1 + 0xE0
+ c2 = c2 + 0x80
+ c3 = c3 + 0x80
+ if rest then
+ return char(c1, c2, c3, rest)
+ end
+ return char(c1, c2, c3)
+ elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st
+ if f_str_surrogate_prev == 0 then
+ f_str_surrogate_prev = ucode
+ if not rest then
+ return ''
+ end
+ surrogate_first_error()
+ end
+ f_str_surrogate_prev = 0
+ surrogate_first_error()
+ else -- surrogate pair 2nd
+ if f_str_surrogate_prev ~= 0 then
+ ucode = 0x10000 +
+ (f_str_surrogate_prev - 0xD800) * 0x400 +
+ (ucode - 0xDC00)
+ f_str_surrogate_prev = 0
+ c1 = floor(ucode / 0x40000)
+ ucode = ucode - c1 * 0x40000
+ c2 = floor(ucode / 0x1000)
+ ucode = ucode - c2 * 0x1000
+ c3 = floor(ucode / 0x40)
+ c4 = ucode - c3 * 0x40
+ c1 = c1 + 0xF0
+ c2 = c2 + 0x80
+ c3 = c3 + 0x80
+ c4 = c4 + 0x80
+ if rest then
+ return char(c1, c2, c3, c4, rest)
+ end
+ return char(c1, c2, c3, c4)
+ end
+ decode_error("2nd surrogate pair byte appeared without 1st")
+ end
+ end
+ decode_error("invalid unicode codepoint literal")
+ end
+ if f_str_surrogate_prev ~= 0 then
+ f_str_surrogate_prev = 0
+ surrogate_first_error()
+ end
+ return f_str_escapetbl[ch] .. ucode
+ end
+
+ -- caching interpreted keys for speed
+ local f_str_keycache = setmetatable({}, {__mode="v"})
+
+ local function f_str(iskey)
+ local newpos = pos
+ local tmppos, c1, c2
+ repeat
+ newpos = find(json, '"', newpos, true) -- search '"'
+ if not newpos then
+ decode_error("unterminated string")
+ end
+ tmppos = newpos-1
+ newpos = newpos+1
+ c1, c2 = byte(json, tmppos-1, tmppos)
+ if c2 == 0x5C and c1 == 0x5C then -- skip preceding '\\'s
+ repeat
+ tmppos = tmppos-2
+ c1, c2 = byte(json, tmppos-1, tmppos)
+ until c2 ~= 0x5C or c1 ~= 0x5C
+ tmppos = newpos-2
+ end
+ until c2 ~= 0x5C -- leave if '"' is not preceded by '\'
+
+ local str = sub(json, pos, tmppos)
+ pos = newpos
+
+ if iskey then -- check key cache
+ tmppos = f_str_keycache[str] -- reuse tmppos for cache key/val
+ if tmppos then
+ return tmppos
+ end
+ tmppos = str
+ end
+
+ if find(str, f_str_ctrl_pat) then
+ decode_error("unescaped control string")
+ end
+ if find(str, '\\', 1, true) then -- check whether a backslash exists
+ -- We need to grab 4 characters after the escape char,
+ -- for encoding unicode codepoint to UTF-8.
+ -- As we need to ensure that every first surrogate pair byte is
+ -- immediately followed by second one, we grab upto 5 characters and
+ -- check the last for this purpose.
+ str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst)
+ if f_str_surrogate_prev ~= 0 then
+ f_str_surrogate_prev = 0
+ decode_error("1st surrogate pair byte not continued by 2nd")
+ end
+ end
+ if iskey then -- commit key cache
+ f_str_keycache[tmppos] = str
+ end
+ return str
+ end
+
+ --[[
+ Arrays, Objects
+ --]]
+ -- array
+ local function f_ary()
+ rec_depth = rec_depth + 1
+ if rec_depth > 1000 then
+ decode_error('too deeply nested json (> 1000)')
+ end
+ local ary = {}
+
+ pos = match(json, '^[ \n\r\t]*()', pos)
+
+ local i = 0
+ if byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty
+ pos = pos+1
+ else
+ local newpos = pos
+ repeat
+ i = i+1
+ f = dispatcher[byte(json,newpos)] -- parse value
+ pos = newpos+1
+ ary[i] = f()
+ newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma
+ until not newpos
+
+ newpos = match(json, '^[ \n\r\t]*%]()', pos) -- check closing bracket
+ if not newpos then
+ decode_error("no closing bracket of an array")
+ end
+ pos = newpos
+ end
+
+ if arraylen then -- commit the length of the array if `arraylen` is set
+ ary[0] = i
+ end
+ rec_depth = rec_depth - 1
+ return ary
+ end
+
+ -- objects
+ local function f_obj()
+ rec_depth = rec_depth + 1
+ if rec_depth > 1000 then
+ decode_error('too deeply nested json (> 1000)')
+ end
+ local obj = {}
+
+ pos = match(json, '^[ \n\r\t]*()', pos)
+ if byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty
+ pos = pos+1
+ else
+ local newpos = pos
+
+ repeat
+ if byte(json, newpos) ~= 0x22 then -- check '"'
+ decode_error("not key")
+ end
+ pos = newpos+1
+ local key = f_str(true) -- parse key
+
+ -- optimized for compact json
+ -- c1, c2 == ':', <the first char of the value> or
+ -- c1, c2, c3 == ':', ' ', <the first char of the value>
+ f = f_err
+ local c1, c2, c3 = byte(json, pos, pos+3)
+ if c1 == 0x3A then
+ if c2 ~= 0x20 then
+ f = dispatcher[c2]
+ newpos = pos+2
+ else
+ f = dispatcher[c3]
+ newpos = pos+3
+ end
+ end
+ if f == f_err then -- read a colon and arbitrary number of spaces
+ newpos = match(json, '^[ \n\r\t]*:[ \n\r\t]*()', pos)
+ if not newpos then
+ decode_error("no colon after a key")
+ end
+ f = dispatcher[byte(json, newpos)]
+ newpos = newpos+1
+ end
+ pos = newpos
+ obj[key] = f() -- parse value
+ newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos)
+ until not newpos
+
+ newpos = match(json, '^[ \n\r\t]*}()', pos)
+ if not newpos then
+ decode_error("no closing bracket of an object")
+ end
+ pos = newpos
+ end
+
+ rec_depth = rec_depth - 1
+ return obj
+ end
+
+ --[[
+ The jump table to dispatch a parser for a value,
+ indexed by the code of the value's first char.
+ Nil key means the end of json.
+ --]]
+ dispatcher = { [0] =
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,
+ f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num,
+ f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,
+ f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,
+ __index = function()
+ decode_error("unexpected termination")
+ end
+ }
+ setmetatable(dispatcher, dispatcher)
+
+ --[[
+ run decoder
+ --]]
+ local function decode(json_, pos_, nullv_, arraylen_)
+ json, pos, nullv, arraylen = json_, pos_, nullv_, arraylen_
+ rec_depth = 0
+
+ pos = match(json, '^[ \n\r\t]*()', pos)
+
+ f = dispatcher[byte(json, pos)]
+ pos = pos+1
+ local v = f()
+
+ if pos_ then
+ return v, pos
+ else
+ f, pos = find(json, '^[ \n\r\t]*', pos)
+ if pos ~= #json then
+ decode_error('json ended')
+ end
+ return v
+ end
+ end
+
+ return decode
+end
+
+return newdecoder
A scripts/lua/share/lunajson/encoder.lua => scripts/lua/share/lunajson/encoder.lua +185 -0
@@ 0,0 1,185 @@
+local error = error
+local byte, find, format, gsub, match = string.byte, string.find, string.format, string.gsub, string.match
+local concat = table.concat
+local tostring = tostring
+local pairs, type = pairs, type
+local setmetatable = setmetatable
+local huge, tiny = 1/0, -1/0
+
+local f_string_esc_pat
+if _VERSION == "Lua 5.1" then
+ -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
+ f_string_esc_pat = '[^ -!#-[%]^-\255]'
+else
+ f_string_esc_pat = '[\0-\31"\\]'
+end
+
+local _ENV = nil
+
+
+local function newencoder()
+ local v, nullv
+ local i, builder, visited
+
+ local function f_tostring(v)
+ builder[i] = tostring(v)
+ i = i+1
+ end
+
+ local radixmark = match(tostring(0.5), '[^0-9]')
+ local delimmark = match(tostring(12345.12345), '[^0-9' .. radixmark .. ']')
+ if radixmark == '.' then
+ radixmark = nil
+ end
+
+ local radixordelim
+ if radixmark or delimmark then
+ radixordelim = true
+ if radixmark and find(radixmark, '%W') then
+ radixmark = '%' .. radixmark
+ end
+ if delimmark and find(delimmark, '%W') then
+ delimmark = '%' .. delimmark
+ end
+ end
+
+ local f_number = function(n)
+ if tiny < n and n < huge then
+ local s = format("%.17g", n)
+ if radixordelim then
+ if delimmark then
+ s = gsub(s, delimmark, '')
+ end
+ if radixmark then
+ s = gsub(s, radixmark, '.')
+ end
+ end
+ builder[i] = s
+ i = i+1
+ return
+ end
+ error('invalid number')
+ end
+
+ local doencode
+
+ local f_string_subst = {
+ ['"'] = '\\"',
+ ['\\'] = '\\\\',
+ ['\b'] = '\\b',
+ ['\f'] = '\\f',
+ ['\n'] = '\\n',
+ ['\r'] = '\\r',
+ ['\t'] = '\\t',
+ __index = function(_, c)
+ return format('\\u00%02X', byte(c))
+ end
+ }
+ setmetatable(f_string_subst, f_string_subst)
+
+ local function f_string(s)
+ builder[i] = '"'
+ if find(s, f_string_esc_pat) then
+ s = gsub(s, f_string_esc_pat, f_string_subst)
+ end
+ builder[i+1] = s
+ builder[i+2] = '"'
+ i = i+3
+ end
+
+ local function f_table(o)
+ if visited[o] then
+ error("loop detected")
+ end
+ visited[o] = true
+
+ local tmp = o[0]
+ if type(tmp) == 'number' then -- arraylen available
+ builder[i] = '['
+ i = i+1
+ for j = 1, tmp do
+ doencode(o[j])
+ builder[i] = ','
+ i = i+1
+ end
+ if tmp > 0 then
+ i = i-1
+ end
+ builder[i] = ']'
+
+ else
+ tmp = o[1]
+ if tmp ~= nil then -- detected as array
+ builder[i] = '['
+ i = i+1
+ local j = 2
+ repeat
+ doencode(tmp)
+ tmp = o[j]
+ if tmp == nil then
+ break
+ end
+ j = j+1
+ builder[i] = ','
+ i = i+1
+ until false
+ builder[i] = ']'
+
+ else -- detected as object
+ builder[i] = '{'
+ i = i+1
+ local tmp = i
+ for k, v in pairs(o) do
+ if type(k) ~= 'string' then
+ error("non-string key")
+ end
+ f_string(k)
+ builder[i] = ':'
+ i = i+1
+ doencode(v)
+ builder[i] = ','
+ i = i+1
+ end
+ if i > tmp then
+ i = i-1
+ end
+ builder[i] = '}'
+ end
+ end
+
+ i = i+1
+ visited[o] = nil
+ end
+
+ local dispatcher = {
+ boolean = f_tostring,
+ number = f_number,
+ string = f_string,
+ table = f_table,
+ __index = function()
+ error("invalid type value")
+ end
+ }
+ setmetatable(dispatcher, dispatcher)
+
+ function doencode(v)
+ if v == nullv then
+ builder[i] = 'null'
+ i = i+1
+ return
+ end
+ return dispatcher[type(v)](v)
+ end
+
+ local function encode(v_, nullv_)
+ v, nullv = v_, nullv_
+ i, builder, visited = 1, {}, {}
+
+ doencode(v)
+ return concat(builder)
+ end
+
+ return encode
+end
+
+return newencoder
A scripts/lua/share/lunajson/sax.lua => scripts/lua/share/lunajson/sax.lua +719 -0
@@ 0,0 1,719 @@
+local setmetatable, tonumber, tostring =
+ setmetatable, tonumber, tostring
+local floor, inf =
+ math.floor, math.huge
+local mininteger, tointeger =
+ math.mininteger or nil, math.tointeger or nil
+local byte, char, find, gsub, match, sub =
+ string.byte, string.char, string.find, string.gsub, string.match, string.sub
+
+local function _parse_error(pos, errmsg)
+ error("parse error at " .. pos .. ": " .. errmsg, 2)
+end
+
+local f_str_ctrl_pat
+if _VERSION == "Lua 5.1" then
+ -- use the cluttered pattern because lua 5.1 does not handle \0 in a pattern correctly
+ f_str_ctrl_pat = '[^\32-\255]'
+else
+ f_str_ctrl_pat = '[\0-\31]'
+end
+
+local type, unpack = type, table.unpack or unpack
+local open = io.open
+
+local _ENV = nil
+
+
+local function nop() end
+
+local function newparser(src, saxtbl)
+ local json, jsonnxt, rec_depth
+ local jsonlen, pos, acc = 0, 1, 0
+
+ -- `f` is the temporary for dispatcher[c] and
+ -- the dummy for the first return value of `find`
+ local dispatcher, f
+
+ -- initialize
+ if type(src) == 'string' then
+ json = src
+ jsonlen = #json
+ jsonnxt = function()
+ json = ''
+ jsonlen = 0
+ jsonnxt = nop
+ end
+ else
+ jsonnxt = function()
+ acc = acc + jsonlen
+ pos = 1
+ repeat
+ json = src()
+ if not json then
+ json = ''
+ jsonlen = 0
+ jsonnxt = nop
+ return
+ end
+ jsonlen = #json
+ until jsonlen > 0
+ end
+ jsonnxt()
+ end
+
+ local sax_startobject = saxtbl.startobject or nop
+ local sax_key = saxtbl.key or nop
+ local sax_endobject = saxtbl.endobject or nop
+ local sax_startarray = saxtbl.startarray or nop
+ local sax_endarray = saxtbl.endarray or nop
+ local sax_string = saxtbl.string or nop
+ local sax_number = saxtbl.number or nop
+ local sax_boolean = saxtbl.boolean or nop
+ local sax_null = saxtbl.null or nop
+
+ --[[
+ Helper
+ --]]
+ local function tryc()
+ local c = byte(json, pos)
+ if not c then
+ jsonnxt()
+ c = byte(json, pos)
+ end
+ return c
+ end
+
+ local function parse_error(errmsg)
+ return _parse_error(acc + pos, errmsg)
+ end
+
+ local function tellc()
+ return tryc() or parse_error("unexpected termination")
+ end
+
+ local function spaces() -- skip spaces and prepare the next char
+ while true do
+ pos = match(json, '^[ \n\r\t]*()', pos)
+ if pos <= jsonlen then
+ return
+ end
+ if jsonlen == 0 then
+ parse_error("unexpected termination")
+ end
+ jsonnxt()
+ end
+ end
+
+ --[[
+ Invalid
+ --]]
+ local function f_err()
+ parse_error('invalid value')
+ end
+
+ --[[
+ Constants
+ --]]
+ -- fallback slow constants parser
+ local function generic_constant(target, targetlen, ret, sax_f)
+ for i = 1, targetlen do
+ local c = tellc()
+ if byte(target, i) ~= c then
+ parse_error("invalid char")
+ end
+ pos = pos+1
+ end
+ return sax_f(ret)
+ end
+
+ -- null
+ local function f_nul()
+ if sub(json, pos, pos+2) == 'ull' then
+ pos = pos+3
+ return sax_null(nil)
+ end
+ return generic_constant('ull', 3, nil, sax_null)
+ end
+
+ -- false
+ local function f_fls()
+ if sub(json, pos, pos+3) == 'alse' then
+ pos = pos+4
+ return sax_boolean(false)
+ end
+ return generic_constant('alse', 4, false, sax_boolean)
+ end
+
+ -- true
+ local function f_tru()
+ if sub(json, pos, pos+2) == 'rue' then
+ pos = pos+3
+ return sax_boolean(true)
+ end
+ return generic_constant('rue', 3, true, sax_boolean)
+ end
+
+ --[[
+ Numbers
+ Conceptually, the longest prefix that matches to `[-+.0-9A-Za-z]+` (in regexp)
+ is captured as a number and its conformance to the JSON spec is checked.
+ --]]
+ -- deal with non-standard locales
+ local radixmark = match(tostring(0.5), '[^0-9]')
+ local fixedtonumber = tonumber
+ if radixmark ~= '.' then
+ if find(radixmark, '%W') then
+ radixmark = '%' .. radixmark
+ end
+ fixedtonumber = function(s)
+ return tonumber(gsub(s, '.', radixmark))
+ end
+ end
+
+ local function number_error()
+ return parse_error('invalid number')
+ end
+
+ -- fallback slow parser
+ local function generic_number(mns)
+ local buf = {}
+ local i = 1
+ local is_int = true
+
+ local c = byte(json, pos)
+ pos = pos+1
+
+ local function nxt()
+ buf[i] = c
+ i = i+1
+ c = tryc()
+ pos = pos+1
+ end
+
+ if c == 0x30 then
+ nxt()
+ if c and 0x30 <= c and c < 0x3A then
+ number_error()
+ end
+ else
+ repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
+ end
+ if c == 0x2E then
+ is_int = false
+ nxt()
+ if not (c and 0x30 <= c and c < 0x3A) then
+ number_error()
+ end
+ repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
+ end
+ if c == 0x45 or c == 0x65 then
+ is_int = false
+ nxt()
+ if c == 0x2B or c == 0x2D then
+ nxt()
+ end
+ if not (c and 0x30 <= c and c < 0x3A) then
+ number_error()
+ end
+ repeat nxt() until not (c and 0x30 <= c and c < 0x3A)
+ end
+ if c and (0x41 <= c and c <= 0x5B or
+ 0x61 <= c and c <= 0x7B or
+ c == 0x2B or c == 0x2D or c == 0x2E) then
+ number_error()
+ end
+ pos = pos-1
+
+ local num = char(unpack(buf))
+ num = fixedtonumber(num)
+ if mns then
+ num = -num
+ if num == mininteger and is_int then
+ num = mininteger
+ end
+ end
+ return sax_number(num)
+ end
+
+ -- `0(\.[0-9]*)?([eE][+-]?[0-9]*)?`
+ local function f_zro(mns)
+ local num, c = match(json, '^(%.?[0-9]*)([-+.A-Za-z]?)', pos) -- skipping 0
+
+ if num == '' then
+ if pos > jsonlen then
+ pos = pos - 1
+ return generic_number(mns)
+ end
+ if c == '' then
+ if mns then
+ return sax_number(-0.0)
+ end
+ return sax_number(0)
+ end
+
+ if c == 'e' or c == 'E' then
+ num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
+ if c == '' then
+ pos = pos + #num
+ if pos > jsonlen then
+ pos = pos - #num - 1
+ return generic_number(mns)
+ end
+ if mns then
+ return sax_number(-0.0)
+ end
+ return sax_number(0.0)
+ end
+ end
+ pos = pos-1
+ return generic_number(mns)
+ end
+
+ if byte(num) ~= 0x2E or byte(num, -1) == 0x2E then
+ pos = pos-1
+ return generic_number(mns)
+ end
+
+ if c ~= '' then
+ if c == 'e' or c == 'E' then
+ num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
+ end
+ if c ~= '' then
+ pos = pos-1
+ return generic_number(mns)
+ end
+ end
+
+ pos = pos + #num
+ if pos > jsonlen then
+ pos = pos - #num - 1
+ return generic_number(mns)
+ end
+ c = fixedtonumber(num)
+
+ if mns then
+ c = -c
+ end
+ return sax_number(c)
+ end
+
+ -- `[1-9][0-9]*(\.[0-9]*)?([eE][+-]?[0-9]*)?`
+ local function f_num(mns)
+ pos = pos-1
+ local num, c = match(json, '^([0-9]+%.?[0-9]*)([-+.A-Za-z]?)', pos)
+ if byte(num, -1) == 0x2E then -- error if ended with period
+ return generic_number(mns)
+ end
+
+ if c ~= '' then
+ if c ~= 'e' and c ~= 'E' then
+ return generic_number(mns)
+ end
+ num, c = match(json, '^([^eE]*[eE][-+]?[0-9]+)([-+.A-Za-z]?)', pos)
+ if not num or c ~= '' then
+ return generic_number(mns)
+ end
+ end
+
+ pos = pos + #num
+ if pos > jsonlen then
+ pos = pos - #num
+ return generic_number(mns)
+ end
+ c = fixedtonumber(num)
+
+ if mns then
+ c = -c
+ if c == mininteger and not find(num, '[^0-9]') then
+ c = mininteger
+ end
+ end
+ return sax_number(c)
+ end
+
+ -- skip minus sign
+ local function f_mns()
+ local c = byte(json, pos) or tellc()
+ if c then
+ pos = pos+1
+ if c > 0x30 then
+ if c < 0x3A then
+ return f_num(true)
+ end
+ else
+ if c > 0x2F then
+ return f_zro(true)
+ end
+ end
+ end
+ parse_error("invalid number")
+ end
+
+ --[[
+ Strings
+ --]]
+ local f_str_hextbl = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7,
+ 0x8, 0x9, inf, inf, inf, inf, inf, inf,
+ inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, inf,
+ inf, inf, inf, inf, inf, inf, inf, inf,
+ inf, inf, inf, inf, inf, inf, inf, inf,
+ inf, inf, inf, inf, inf, inf, inf, inf,
+ inf, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
+ __index = function()
+ return inf
+ end
+ }
+ setmetatable(f_str_hextbl, f_str_hextbl)
+
+ local f_str_escapetbl = {
+ ['"'] = '"',
+ ['\\'] = '\\',
+ ['/'] = '/',
+ ['b'] = '\b',
+ ['f'] = '\f',
+ ['n'] = '\n',
+ ['r'] = '\r',
+ ['t'] = '\t',
+ __index = function()
+ parse_error("invalid escape sequence")
+ end
+ }
+ setmetatable(f_str_escapetbl, f_str_escapetbl)
+
+ local function surrogate_first_error()
+ return parse_error("1st surrogate pair byte not continued by 2nd")
+ end
+
+ local f_str_surrogate_prev = 0
+ local function f_str_subst(ch, ucode)
+ if ch == 'u' then
+ local c1, c2, c3, c4, rest = byte(ucode, 1, 5)
+ ucode = f_str_hextbl[c1-47] * 0x1000 +
+ f_str_hextbl[c2-47] * 0x100 +
+ f_str_hextbl[c3-47] * 0x10 +
+ f_str_hextbl[c4-47]
+ if ucode ~= inf then
+ if ucode < 0x80 then -- 1byte
+ if rest then
+ return char(ucode, rest)
+ end
+ return char(ucode)
+ elseif ucode < 0x800 then -- 2bytes
+ c1 = floor(ucode / 0x40)
+ c2 = ucode - c1 * 0x40
+ c1 = c1 + 0xC0
+ c2 = c2 + 0x80
+ if rest then
+ return char(c1, c2, rest)
+ end
+ return char(c1, c2)
+ elseif ucode < 0xD800 or 0xE000 <= ucode then -- 3bytes
+ c1 = floor(ucode / 0x1000)
+ ucode = ucode - c1 * 0x1000
+ c2 = floor(ucode / 0x40)
+ c3 = ucode - c2 * 0x40
+ c1 = c1 + 0xE0
+ c2 = c2 + 0x80
+ c3 = c3 + 0x80
+ if rest then
+ return char(c1, c2, c3, rest)
+ end
+ return char(c1, c2, c3)
+ elseif 0xD800 <= ucode and ucode < 0xDC00 then -- surrogate pair 1st
+ if f_str_surrogate_prev == 0 then
+ f_str_surrogate_prev = ucode
+ if not rest then
+ return ''
+ end
+ surrogate_first_error()
+ end
+ f_str_surrogate_prev = 0
+ surrogate_first_error()
+ else -- surrogate pair 2nd
+ if f_str_surrogate_prev ~= 0 then
+ ucode = 0x10000 +
+ (f_str_surrogate_prev - 0xD800) * 0x400 +
+ (ucode - 0xDC00)
+ f_str_surrogate_prev = 0
+ c1 = floor(ucode / 0x40000)
+ ucode = ucode - c1 * 0x40000
+ c2 = floor(ucode / 0x1000)
+ ucode = ucode - c2 * 0x1000
+ c3 = floor(ucode / 0x40)
+ c4 = ucode - c3 * 0x40
+ c1 = c1 + 0xF0
+ c2 = c2 + 0x80
+ c3 = c3 + 0x80
+ c4 = c4 + 0x80
+ if rest then
+ return char(c1, c2, c3, c4, rest)
+ end
+ return char(c1, c2, c3, c4)
+ end
+ parse_error("2nd surrogate pair byte appeared without 1st")
+ end
+ end
+ parse_error("invalid unicode codepoint literal")
+ end
+ if f_str_surrogate_prev ~= 0 then
+ f_str_surrogate_prev = 0
+ surrogate_first_error()
+ end
+ return f_str_escapetbl[ch] .. ucode
+ end
+
+ local function f_str(iskey)
+ local pos2 = pos
+ local newpos
+ local str = ''
+ local bs
+ while true do
+ while true do -- search '\' or '"'
+ newpos = find(json, '[\\"]', pos2)
+ if newpos then
+ break
+ end
+ str = str .. sub(json, pos, jsonlen)
+ if pos2 == jsonlen+2 then
+ pos2 = 2
+ else
+ pos2 = 1
+ end
+ jsonnxt()
+ if jsonlen == 0 then
+ parse_error("unterminated string")
+ end
+ end
+ if byte(json, newpos) == 0x22 then -- break if '"'
+ break
+ end
+ pos2 = newpos+2 -- skip '\<char>'
+ bs = true -- mark the existence of a backslash
+ end
+ str = str .. sub(json, pos, newpos-1)
+ pos = newpos+1
+
+ if find(str, f_str_ctrl_pat) then
+ parse_error("unescaped control string")
+ end
+ if bs then -- a backslash exists
+ -- We need to grab 4 characters after the escape char,
+ -- for encoding unicode codepoint to UTF-8.
+ -- As we need to ensure that every first surrogate pair byte is
+ -- immediately followed by second one, we grab upto 5 characters and
+ -- check the last for this purpose.
+ str = gsub(str, '\\(.)([^\\]?[^\\]?[^\\]?[^\\]?[^\\]?)', f_str_subst)
+ if f_str_surrogate_prev ~= 0 then
+ f_str_surrogate_prev = 0
+ parse_error("1st surrogate pair byte not continued by 2nd")
+ end
+ end
+
+ if iskey then
+ return sax_key(str)
+ end
+ return sax_string(str)
+ end
+
+ --[[
+ Arrays, Objects
+ --]]
+ -- arrays
+ local function f_ary()
+ rec_depth = rec_depth + 1
+ if rec_depth > 1000 then
+ parse_error('too deeply nested json (> 1000)')
+ end
+ sax_startarray()
+
+ spaces()
+ if byte(json, pos) == 0x5D then -- check closing bracket ']' which means the array empty
+ pos = pos+1
+ else
+ local newpos
+ while true do
+ f = dispatcher[byte(json, pos)] -- parse value
+ pos = pos+1
+ f()
+ newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma
+ if newpos then
+ pos = newpos
+ else
+ newpos = match(json, '^[ \n\r\t]*%]()', pos) -- check closing bracket
+ if newpos then
+ pos = newpos
+ break
+ end
+ spaces() -- since the current chunk can be ended, skip spaces toward following chunks
+ local c = byte(json, pos)
+ pos = pos+1
+ if c == 0x2C then -- check comma again
+ spaces()
+ elseif c == 0x5D then -- check closing bracket again
+ break
+ else
+ parse_error("no closing bracket of an array")
+ end
+ end
+ if pos > jsonlen then
+ spaces()
+ end
+ end
+ end
+
+ rec_depth = rec_depth - 1
+ return sax_endarray()
+ end
+
+ -- objects
+ local function f_obj()
+ rec_depth = rec_depth + 1
+ if rec_depth > 1000 then
+ parse_error('too deeply nested json (> 1000)')
+ end
+ sax_startobject()
+
+ spaces()
+ if byte(json, pos) == 0x7D then -- check closing bracket '}' which means the object empty
+ pos = pos+1
+ else
+ local newpos
+ while true do
+ if byte(json, pos) ~= 0x22 then
+ parse_error("not key")
+ end
+ pos = pos+1
+ f_str(true) -- parse key
+ newpos = match(json, '^[ \n\r\t]*:[ \n\r\t]*()', pos) -- check colon
+ if newpos then
+ pos = newpos
+ else
+ spaces() -- read spaces through chunks
+ if byte(json, pos) ~= 0x3A then -- check colon again
+ parse_error("no colon after a key")
+ end
+ pos = pos+1
+ spaces()
+ end
+ if pos > jsonlen then
+ spaces()
+ end
+ f = dispatcher[byte(json, pos)]
+ pos = pos+1
+ f() -- parse value
+ newpos = match(json, '^[ \n\r\t]*,[ \n\r\t]*()', pos) -- check comma
+ if newpos then
+ pos = newpos
+ else
+ newpos = match(json, '^[ \n\r\t]*}()', pos) -- check closing bracket
+ if newpos then
+ pos = newpos
+ break
+ end
+ spaces() -- read spaces through chunks
+ local c = byte(json, pos)
+ pos = pos+1
+ if c == 0x2C then -- check comma again
+ spaces()
+ elseif c == 0x7D then -- check closing bracket again
+ break
+ else
+ parse_error("no closing bracket of an object")
+ end
+ end
+ if pos > jsonlen then
+ spaces()
+ end
+ end
+ end
+
+ rec_depth = rec_depth - 1
+ return sax_endobject()
+ end
+
+ --[[
+ The jump table to dispatch a parser for a value,
+ indexed by the code of the value's first char.
+ Key should be non-nil.
+ --]]
+ dispatcher = { [0] =
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_str, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_mns, f_err, f_err,
+ f_zro, f_num, f_num, f_num, f_num, f_num, f_num, f_num,
+ f_num, f_num, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_ary, f_err, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_fls, f_err,
+ f_err, f_err, f_err, f_err, f_err, f_err, f_nul, f_err,
+ f_err, f_err, f_err, f_err, f_tru, f_err, f_err, f_err,
+ f_err, f_err, f_err, f_obj, f_err, f_err, f_err, f_err,
+ }
+
+ --[[
+ public funcitons
+ --]]
+ local function run()
+ rec_depth = 0
+ spaces()
+ f = dispatcher[byte(json, pos)]
+ pos = pos+1
+ f()
+ end
+
+ local function read(n)
+ if n < 0 then
+ error("the argument must be non-negative")
+ end
+ local pos2 = (pos-1) + n
+ local str = sub(json, pos, pos2)
+ while pos2 > jsonlen and jsonlen ~= 0 do
+ jsonnxt()
+ pos2 = pos2 - (jsonlen - (pos-1))
+ str = str .. sub(json, pos, pos2)
+ end
+ if jsonlen ~= 0 then
+ pos = pos2+1
+ end
+ return str
+ end
+
+ local function tellpos()
+ return acc + pos
+ end
+
+ return {
+ run = run,
+ tryc = tryc,
+ read = read,
+ tellpos = tellpos,
+ }
+end
+
+local function newfileparser(fn, saxtbl)
+ local fp = open(fn)
+ local function gen()
+ local s
+ if fp then
+ s = fp:read(8192)
+ if not s then
+ fp:close()
+ fp = nil
+ end
+ end
+ return s
+ end
+ return newparser(gen, saxtbl)
+end
+
+return {
+ newparser = newparser,
+ newfileparser = newfileparser
+}
A scripts/lua/update.lua => scripts/lua/update.lua +11 -0
@@ 0,0 1,11 @@
+local update = {}
+
+update.script_name = "update.lua"
+update.img_in_progress = "assets/gui_image_update_in_progress.bin"
+update.img_success = "assets/gui_image_update_success.bin"
+update.img_failure = "assets/gui_image_update_failed.bin"
+
+function update.execute()
+end
+
+return update<
\ No newline at end of file