Update editor scripts

This commit is contained in:
Insality
2025-04-19 18:03:50 +03:00
parent 8d2b8c25a0
commit 50e59d9469
19 changed files with 1118 additions and 280 deletions

View File

@@ -0,0 +1,179 @@
local M = {}
-- Define a set of keys that should not have quotes
M.string_keys = {
text = true,
id = true,
value = true,
rename_patterns = true,
}
M.ALWAYS_LIST = {
attributes = true,
nodes = true,
images = true,
children = true,
fonts = true,
layers = true,
textures = true,
embedded_components = true,
embedded_instances = true,
collection_instances = true,
instances = true,
}
M.with_dot_params = {
"x",
"y",
"z",
"w",
"alpha",
"outline_alpha",
"shadow_alpha",
"text_leading",
"text_tracking",
"pieFillAngle",
"innerRadius",
"leading",
"tracking",
"data",
"t_x",
"t_y",
"spread",
"start_delay",
"inherit_velocity",
"start_delay_spread",
"duration_spread",
"start_offset",
"outline_width",
"shadow_x",
"shadow_y",
"aspect_ratio",
"far_z",
"mass",
"linear_damping",
"angular_damping",
"gain",
"pan",
"speed",
"duration"
}
M.KEY_ORDER = {
["font"] = {
"extrude_borders",
"images",
"inner_padding",
"margin",
"font",
"material",
"size",
"antialias",
"alpha",
"outline_alpha",
"outline_width",
"shadow_alpha",
"shadow_blur",
"shadow_x",
"shadow_y",
"extra_characters",
"output_format",
"all_chars",
"cache_width",
"cache_height",
"render_mode",
},
["atlas"] = {
"id",
"images",
"playback",
"fps",
"flip_horizontal",
"flip_vertical",
"image",
"sprite_trim_mode",
"images",
"animations",
"margin",
"extrude_borders",
"inner_padding",
"max_page_width",
"max_page_height",
"rename_patterns",
},
["gui"] = {
"position",
"rotation",
"scale",
"size",
"color",
"type",
"blend_mode",
"text",
"texture",
"font",
"id",
"xanchor",
"yanchor",
"pivot",
"outline",
"shadow",
"adjust_mode",
"line_break",
"parent",
"layer",
"inherit_alpha",
"slice9",
"outerBounds",
"innerRadius",
"perimeterVertices",
"pieFillAngle",
"clipping_mode",
"clipping_visible",
"clipping_inverted",
"alpha",
"outline_alpha",
"shadow_alpha",
"overridden_fields",
"template",
"template_node_child",
"text_leading",
"text_tracking",
"size_mode",
"spine_scene",
"spine_default_animation",
"spine_skin",
"spine_node_child",
"particlefx",
"custom_type",
"enabled",
"visible",
-- Scene
"scripts",
"fonts",
"textures",
"background_color",
"nodes",
"layers",
"material",
"layouts",
"adjust_reference",
"max_nodes",
"spine_scenes",
"particlefxs",
"resources",
"materials",
"max_dynamic_textures",
-- Vectors
"x",
"y",
"z",
"w",
},
}
return M

View File

@@ -0,0 +1,139 @@
local config = require("druid.editor_scripts.defold_parser.system.config")
local M = {}
-- Example: "name: value"
M.REGEX_KEY_COLUM_VALUE = "^%s*([%w_]+):%s*(.+)$"
-- Example: "name {"
M.REGEX_START_TABLE = "^%s*([%w_]*)%s*{%s*$"
-- Example: "}"
M.REGEX_END_TABLE = "^%s*}%s*$"
---@param value string
---@return string
function M.unescape_text_field(value)
-- Splitting the value by new lines and processing each line
local lines = {}
for line in value:gmatch("[^\r\n]+") do
line = line:gsub('\\"', '"') -- Unescaping quotes
line = line:gsub("\\n", "") -- Removing newline escapes
line = line:gsub("\\", "") -- Unescaping backslashes
table.insert(lines, line)
end
-- Reconstructing the value
value = table.concat(lines, "\n")
return value
end
function M.is_multiline_value(value)
return value:find("\\n\"") ~= nil
end
---@param value any
---@param property_name string|nil
---@return any
function M.decode_value(value, property_name)
if value:match('^".*"$') then
-- Removing the quotes from the string
value = value:sub(2, -2)
-- Check if value is escaped
-- If ends with \n
if value:sub(-2) == "\\n" then
value = value:gsub('\\"', '"') -- Unescaping quotes
value = value:gsub("\\n", "")
value = value:gsub("\\", "")
end
elseif value:match('^%-?[0-9.E%-]+$') then
-- Converting to number
value = tonumber(value)
end
-- Specific handling for the "text" property
if property_name == "text" then
value = tostring(value)
else
if value == "true" then
value = true
elseif value == "false" then
value = false
end
end
if property_name == "text" and M.is_multiline_value(value) and type(value) == "string" then
value = M.unescape_text_field(value)
end
return value
end
---@param parent_object table
---@param name string
---@param stack table
function M.new_inner_struct(parent_object, name, stack)
local new_object = {}
M.apply_value(parent_object, name, new_object)
local is_object_always_list = config.ALWAYS_LIST[name]
if is_object_always_list and not M.is_array(parent_object[name]) then
parent_object[name] = { parent_object[name] }
end
table.insert(stack, new_object)
end
---Apply value to the object, if the value is already present, convert it to an array
---@param object table
---@param name string
---@param value any
---@return table object
function M.apply_value(object, name, value)
local is_object_always_list = config.ALWAYS_LIST[name]
if object[name] == nil then
object[name] = value
if is_object_always_list then
object[name] = { object[name] }
end
return object
end
-- Convert to array if not already
if not M.is_array(object[name]) then
object[name] = { object[name] }
end
table.insert(object[name], value)
return object
end
---@param object table
---@param value string
---@return table @object
function M.apply_multiline_value(object, name, value)
if object[name] == nil then
object[name] = value
else
object[name] = object[name] .. "\n" .. value
end
return object
end
--- Check if table is array
---@param t table
---@return boolean
function M.is_array(t)
return type(t) == "table" and t[1] ~= nil
end
return M

View File

@@ -0,0 +1,163 @@
local parser = require("druid.editor_scripts.defold_parser.system.parser")
local M = {}
--- Check if table-array contains element
---@param table table
---@param element any
---@return number|boolean index of element or false
function M.contains(table, element)
for index, value in pairs(table) do
if value == element then
return index
end
end
return false
end
---@param file_path string
---@return string|nil, string|nil @success, reason
function M.read_file(file_path)
local file = io.open(file_path, "r")
if file == nil then
return nil, "Could not open file: " .. file_path
end
local content = file:read("*a")
file:close()
return content, nil
end
---@param file_path string
---@param content string
---@return boolean, string|nil @success, reason
function M.write_file(file_path, content)
local file = io.open(file_path, "w")
if file == nil then
return false, "Could not open file: " .. file_path
end
file:write(content)
file:close()
return true, nil
end
---@param line string
function M.unescape_line(line)
-- Trim whitespaces
line = line:match("^%s*(.-)%s*$")
-- Remove first and last quote symbols only if exists
if line:sub(1, 1) == '"' and line:sub(-1) == '"' then
line = line:sub(2, -2)
end
-- Trim whitespaces
line = line:match("^%s*(.-)%s*$")
-- Splitting the value by new lines and processing each line
line = line:gsub('\\"', '"') -- Unescaping quotes
line = line:gsub("\\n", "") -- Removing newline escapes
line = line:gsub("\\", "") -- Unescaping backslashes
return line
end
---@param line string
---@return string, string, string, boolean @new_object_name, name, value, end_struct_flag
function M.split_line(line)
local new_object_name = line:match(parser.REGEX_START_TABLE)
local name, value = line:match(parser.REGEX_KEY_COLUM_VALUE)
local end_struct_flag = line:match(parser.REGEX_END_TABLE)
-- We hit a line what is contains only value, like multiline strings
if not name and not value then
value = line
end
return new_object_name, name, value, end_struct_flag
end
-- what a crap...
local LAST_USED_NAME = nil
---@param unescaped_line string @line to parse
---@param stack table @stack of objects
---@return boolean
function M.parse_line(unescaped_line, stack)
unescaped_line = unescaped_line:match("^%s*(.-)%s*$")
-- Use last object to insert data
local object = stack[#stack]
local line = M.unescape_line(unescaped_line)
local inner_object_name, name, value, end_struct_flag = M.split_line(line)
local is_just_new_line = (unescaped_line == "\"\\n\"")
if not end_struct_flag and (line == "\"" or line == "") and (not is_just_new_line) then
if LAST_USED_NAME ~= "text" then
end_struct_flag = true
end
end
if inner_object_name then
parser.new_inner_struct(object, inner_object_name, stack)
object = stack[#stack]
end
if name and value ~= nil then
-- If value is nested object...
if value:sub(1, 1) == '"' then
value = value:sub(2, -1)
end
if value:sub(-1) == '"' then
value = value:sub(1, -2)
end
local unescape_line = M.unescape_line(value)
local new_object_name, field_name, _, end_flag = M.split_line(unescape_line)
if (new_object_name or field_name or end_flag) and name ~= "text" then
parser.new_inner_struct(object, name, stack)
object = stack[#stack]
M.parse_line(value, stack)
else
-- Just a hack honestly
-- If first character is a quote, then remove it
if value:sub(1, 1) == '"' then
value = value:sub(2, -1)
end
if value:sub(-1) == '"' then
value = value:sub(1, -2)
end
value = parser.decode_value(value, name)
LAST_USED_NAME = name
parser.apply_value(object, name, value)
end
end
if not name and value and not inner_object_name and not end_struct_flag then
-- We should to add value to the last as a multiline data
parser.apply_multiline_value(object, LAST_USED_NAME, value)
end
if end_struct_flag then
-- Go back to the parent object
table.remove(stack)
end
return true
end
return M