Merge branch 'master' into sdl2
This commit is contained in:
commit
1d7e52c387
42 changed files with 2105 additions and 325 deletions
|
@ -104,6 +104,8 @@ Version 1.13.1+dev:
|
|||
These operators are applied after the existing '-' operator that takes the
|
||||
opposite direction.
|
||||
* Adjacency filters in abilities and weapon specials now support count= and is_enemy=
|
||||
* Add new looping tags: [for], [foreach], [repeat]
|
||||
* Add new flow control tags: [break], [continue], [return]
|
||||
* Editor:
|
||||
* Added Category field and color sliders to the Edit Label panel.
|
||||
* Miscellaneous and bug fixes:
|
||||
|
@ -132,6 +134,7 @@ Version 1.13.1+dev:
|
|||
* Fixed possibility of corrupting saved games in certain instances,
|
||||
eg if an add-on tries to set an invalid variable
|
||||
* Fixed bug 23060: unit stat tooltips do not show.
|
||||
* wmllint, wmlscope, wmlindent and wmllint-1.4 now run on Python 3
|
||||
|
||||
Version 1.13.1:
|
||||
* Security fixes:
|
||||
|
|
|
@ -275,9 +275,11 @@ function wml_actions.music(cfg)
|
|||
wesnoth.set_music(cfg)
|
||||
end
|
||||
|
||||
wml_actions.command = utils.handle_event_commands
|
||||
function wml_actions.command(cfg)
|
||||
utils.handle_event_commands(cfg, "plain")
|
||||
end
|
||||
|
||||
-- since if and while are Lua keywords, we can't create functions with such names
|
||||
-- we can't create functions with names that are Lua keywords (eg if, while)
|
||||
-- instead, we store the following anonymous functions directly into
|
||||
-- the table, using the [] operator, rather than by using the point syntax
|
||||
|
||||
|
@ -288,7 +290,8 @@ wml_actions["if"] = function(cfg)
|
|||
|
||||
if wesnoth.eval_conditional(cfg) then -- evaluate [if] tag
|
||||
for then_child in helper.child_range(cfg, "then") do
|
||||
utils.handle_event_commands(then_child)
|
||||
local action = utils.handle_event_commands(then_child, "conditional")
|
||||
if action ~= "none" then break end
|
||||
end
|
||||
return -- stop after executing [then] tags
|
||||
end
|
||||
|
@ -296,7 +299,8 @@ wml_actions["if"] = function(cfg)
|
|||
for elseif_child in helper.child_range(cfg, "elseif") do
|
||||
if wesnoth.eval_conditional(elseif_child) then -- we'll evaluate the [elseif] tags one by one
|
||||
for then_tag in helper.child_range(elseif_child, "then") do
|
||||
utils.handle_event_commands(then_tag)
|
||||
local action = utils.handle_event_commands(then_tag, "conditional")
|
||||
if action ~= "none" then break end
|
||||
end
|
||||
return -- stop on first matched condition
|
||||
end
|
||||
|
@ -304,7 +308,8 @@ wml_actions["if"] = function(cfg)
|
|||
|
||||
-- no matched condition, try the [else] tags
|
||||
for else_child in helper.child_range(cfg, "else") do
|
||||
utils.handle_event_commands(else_child)
|
||||
local action = utils.handle_event_commands(else_child, "conditional")
|
||||
if action ~= "none" then break end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -313,10 +318,146 @@ wml_actions["while"] = function( cfg )
|
|||
for i = 1, 65536 do
|
||||
if wesnoth.eval_conditional( cfg ) then
|
||||
for do_child in helper.child_range( cfg, "do" ) do
|
||||
utils.handle_event_commands( do_child )
|
||||
local action = utils.handle_event_commands(do_child, "loop")
|
||||
if action == "break" then
|
||||
utils.set_exiting("none")
|
||||
goto exit
|
||||
elseif action == "continue" then
|
||||
utils.set_exiting("none")
|
||||
break
|
||||
elseif action ~= "none" then
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
else return end
|
||||
end
|
||||
::exit::
|
||||
end
|
||||
|
||||
wml_actions["break"] = function(cfg)
|
||||
utils.set_exiting("break")
|
||||
end
|
||||
|
||||
wml_actions["return"] = function(cfg)
|
||||
utils.set_exiting("return")
|
||||
end
|
||||
|
||||
function wml_actions.continue(cfg)
|
||||
utils.set_exiting("continue")
|
||||
end
|
||||
|
||||
wesnoth.wml_actions["for"] = function(cfg)
|
||||
local first, last, step
|
||||
if cfg.array then
|
||||
first = 0
|
||||
last = wesnoth.get_variable(cfg.array .. ".length") - 1
|
||||
step = 1
|
||||
if cfg.reverse == "yes" then
|
||||
first, last = last, first
|
||||
step = -1
|
||||
end
|
||||
else
|
||||
first = cfg.start or 0
|
||||
last = cfg["end"] or first
|
||||
step = cfg.step or ((last - first) / math.abs(last - first))
|
||||
end
|
||||
local i_var = cfg.variable or "i"
|
||||
local save_i = utils.start_var_scope(i_var)
|
||||
wesnoth.set_variable(i_var, first)
|
||||
while wesnoth.get_variable(i_var) <= last do
|
||||
for do_child in helper.child_range( cfg, "do" ) do
|
||||
local action = utils.handle_event_commands(do_child, "loop")
|
||||
if action == "break" then
|
||||
utils.set_exiting("none")
|
||||
goto exit
|
||||
elseif action == "continue" then
|
||||
utils.set_exiting("none")
|
||||
break
|
||||
elseif action ~= "none" then
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
wesnoth.set_variable(i_var, wesnoth.get_variable(i_var) + 1)
|
||||
end
|
||||
::exit::
|
||||
utils.end_var_scope(i_var, save_i)
|
||||
end
|
||||
|
||||
wml_actions["repeat"] = function(cfg)
|
||||
local times = cfg.times or 1
|
||||
for i = 1, times do
|
||||
for do_child in helper.child_range( cfg, "do" ) do
|
||||
local action = utils.handle_event_commands(do_child, "loop")
|
||||
if action == "break" then
|
||||
utils.set_exiting("none")
|
||||
return
|
||||
elseif action == "continue" then
|
||||
utils.set_exiting("none")
|
||||
break
|
||||
elseif action ~= "none" then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function wml_actions.foreach(cfg)
|
||||
local array_name = cfg.variable or helper.wml_error "[foreach] missing required variable= attribute"
|
||||
local array = helper.get_variable_array(array_name)
|
||||
if #array == 0 then return end -- empty and scalars unwanted
|
||||
local item_name = cfg.item_var or "this_item"
|
||||
local this_item = utils.start_var_scope(item_name) -- if this_item is already set
|
||||
local i_name = cfg.index_var or "i"
|
||||
local i = utils.start_var_scope(i_name) -- if i is already set
|
||||
local array_length = wesnoth.get_variable(array_name .. ".length")
|
||||
|
||||
for index, value in ipairs(array) do
|
||||
-- Some protection against external modification
|
||||
-- It's not perfect, though - it'd be nice if *any* change could be detected
|
||||
if array_length ~= wesnoth.get_variable(array_name .. ".length") then
|
||||
helper.wml_error("WML array length changed during [foreach] iteration")
|
||||
end
|
||||
wesnoth.set_variable(item_name, value)
|
||||
-- set index variable
|
||||
wesnoth.set_variable(i_name, index-1) -- here -1, because of WML array
|
||||
-- perform actions
|
||||
for do_child in helper.child_range(cfg, "do") do
|
||||
local action = utils.handle_event_commands(do_child, "loop")
|
||||
if action == "break" then
|
||||
utils.set_exiting("none")
|
||||
goto exit
|
||||
elseif action == "continue" then
|
||||
utils.set_exiting("none")
|
||||
break
|
||||
elseif action ~= "none" then
|
||||
goto exit
|
||||
end
|
||||
end
|
||||
-- set back the content, in case the author made some modifications
|
||||
if not cfg.readonly then
|
||||
array[index] = wesnoth.get_variable(item_name)
|
||||
end
|
||||
end
|
||||
::exit::
|
||||
|
||||
-- house cleaning
|
||||
utils.end_var_scope(item_name)
|
||||
utils.end_var_scope(i)
|
||||
|
||||
--[[
|
||||
This forces the readonly key to be taken literally.
|
||||
|
||||
If readonly=yes, then this line guarantees that the array
|
||||
is unchanged after the [foreach] loop ends.
|
||||
|
||||
If readonly=no, then this line updates the array with any
|
||||
changes the user has applied through the $this_item
|
||||
variable (or whatever variable was given in item_var).
|
||||
|
||||
Note that altering the array via indexing (with the index_var)
|
||||
is not supported; any such changes will be reverted by this line.
|
||||
]]
|
||||
helper.set_variable_array(array_name, array)
|
||||
end
|
||||
|
||||
function wml_actions.switch(cfg)
|
||||
|
@ -327,17 +468,20 @@ function wml_actions.switch(cfg)
|
|||
for v in helper.child_range(cfg, "case") do
|
||||
for w in utils.split(v.value) do
|
||||
if w == tostring(var_value) then
|
||||
utils.handle_event_commands(v)
|
||||
local action = utils.handle_event_commands(v, "switch")
|
||||
found = true
|
||||
break
|
||||
if action ~= "none" then goto exit end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
::exit::
|
||||
|
||||
-- Otherwise execute [else] statements.
|
||||
if not found then
|
||||
for v in helper.child_range(cfg, "else") do
|
||||
utils.handle_event_commands(v)
|
||||
local action = utils.handle_event_commands(v, "switch")
|
||||
if action ~= "none" then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -71,11 +71,35 @@ function utils.optional_side_filter(cfg, key_name, filter_name)
|
|||
return false
|
||||
end
|
||||
|
||||
function utils.handle_event_commands(cfg)
|
||||
local current_exit = "none"
|
||||
local scope_stack = {
|
||||
push = table.insert,
|
||||
pop = table.remove,
|
||||
}
|
||||
|
||||
--[[ Possible exit types:
|
||||
- none - ordinary execution
|
||||
- break - exiting a loop scope
|
||||
- return - immediate termination (exit all scopes)
|
||||
- continue - jumping to the end of a loop scope
|
||||
]]
|
||||
function utils.set_exiting(exit_type)
|
||||
current_exit = exit_type
|
||||
end
|
||||
|
||||
--[[ Possible scope types:
|
||||
- plain - ordinary scope, no special features; eg [command] or [event]
|
||||
- conditional - scope that's executing because of a condition, eg [then] or [else]
|
||||
- switch - scope that's part of a switch statement, eg [case] or [else]
|
||||
- loop - scope that's part of a loop, eg [do]
|
||||
Currently, only "loop" has any special effects. ]]
|
||||
function utils.handle_event_commands(cfg, scope_type)
|
||||
-- The WML might be modifying the currently executed WML by mixing
|
||||
-- [insert_tag] with [set_variables] and [clear_variable], so we
|
||||
-- have to be careful not to get confused by tags vanishing during
|
||||
-- the execution, hence the manual handling of [insert_tag].
|
||||
scope_type = scope_type or "plain"
|
||||
scope_stack:push(scope_type)
|
||||
local cmds = helper.shallow_literal(cfg)
|
||||
for i = 1,#cmds do
|
||||
local v = cmds[i]
|
||||
|
@ -104,6 +128,7 @@ function utils.handle_event_commands(cfg)
|
|||
local j = 0
|
||||
repeat
|
||||
cmd(arg)
|
||||
if current_exit ~= "none" then break end
|
||||
j = j + 1
|
||||
if j >= wesnoth.get_variable(insert_from .. ".length") then break end
|
||||
arg = wesnoth.tovconfig(wesnoth.get_variable(string.format("%s[%d]", insert_from, j)))
|
||||
|
@ -112,9 +137,18 @@ function utils.handle_event_commands(cfg)
|
|||
cmd(arg)
|
||||
end
|
||||
end
|
||||
if current_exit ~= "none" then break end
|
||||
end
|
||||
scope_stack:pop()
|
||||
if #scope_stack == 0 then
|
||||
if current_exit == "continue" and scope_type ~= "loop" then
|
||||
helper.wml_error("[continue] found outside a loop scope!")
|
||||
end
|
||||
current_exit = "none"
|
||||
end
|
||||
-- Apply music alterations once all the commands have been processed.
|
||||
wesnoth.set_music()
|
||||
return current_exit
|
||||
end
|
||||
|
||||
-- Splits the string argument on commas, excepting those commas that occur
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
local helper = wesnoth.require "lua/helper.lua"
|
||||
local utils = wesnoth.require "lua/wml-utils.lua"
|
||||
local location_set = wesnoth.require "lua/location_set.lua"
|
||||
local _ = wesnoth.textdomain "wesnoth"
|
||||
|
||||
local function log(msg, level)
|
||||
wesnoth.wml_actions.wml_message({
|
||||
|
@ -127,7 +128,7 @@ function wesnoth.wml_actions.message(cfg)
|
|||
|
||||
local options, option_events = {}, {}
|
||||
for option in helper.child_range(cfg, "option") do
|
||||
local condition = helper.get_child(cfg, "show_if") or {}
|
||||
local condition = helper.get_child(option, "show_if") or {}
|
||||
|
||||
if wesnoth.eval_conditional(condition) then
|
||||
table.insert(options, option.message)
|
||||
|
@ -196,7 +197,12 @@ function wesnoth.wml_actions.message(cfg)
|
|||
-- Always show the dialog if it has no input, whether we are replaying or not
|
||||
msg_dlg()
|
||||
else
|
||||
local choice = wesnoth.synchronize_choice(msg_dlg)
|
||||
local wait_description = cfg.wait_description or _("input")
|
||||
if type(sides_for) ~= "number" then
|
||||
-- 0 means currently playing side.
|
||||
sides_for = 0
|
||||
end
|
||||
local choice = wesnoth.synchronize_choice(wait_description, msg_dlg, sides_for)
|
||||
|
||||
option_chosen = tonumber(choice.value)
|
||||
|
||||
|
@ -214,7 +220,8 @@ function wesnoth.wml_actions.message(cfg)
|
|||
end
|
||||
|
||||
for i, cmd in ipairs(option_events[option_chosen]) do
|
||||
utils.handle_event_commands(cmd)
|
||||
local action = utils.handle_event_commands(cmd, "plain")
|
||||
if action ~= "none" then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,8 @@ function wml_actions.object(cfg)
|
|||
end
|
||||
|
||||
for cmd in helper.child_range(cfg, command_type) do
|
||||
utils.handle_event_commands(cmd)
|
||||
local action = utils.handle_event_commands(cmd, "conditional")
|
||||
if action ~= "none" then break end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -56,3 +56,11 @@
|
|||
{CONTENT}
|
||||
[/test]
|
||||
#enddef
|
||||
|
||||
#define FAIL
|
||||
{RETURN ([false][/false])}
|
||||
#enddef
|
||||
|
||||
#define SUCCEED
|
||||
{RETURN ([true][/true])}
|
||||
#enddef
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name = side 2 turn 60
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[event]
|
||||
name = side 2 turn refresh
|
||||
|
|
|
@ -343,7 +343,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name = side 1 turn refresh
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[/test]
|
||||
#enddef
|
||||
|
@ -442,7 +442,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name = side 1 turn refresh
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[/test]
|
||||
#enddef
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
[false][/false]
|
||||
[/not]
|
||||
[then]
|
||||
{RETURN [true][/true]}
|
||||
{SUCCEED}
|
||||
[/then]
|
||||
[/if]
|
||||
[/event]
|
||||
|
@ -35,7 +35,7 @@
|
|||
[false][/false]
|
||||
[/or]
|
||||
[then]
|
||||
{RETURN [true][/true]}
|
||||
{SUCCEED}
|
||||
[/then]
|
||||
[/if]
|
||||
[/event]
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name = side 1 turn 7
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[event]
|
||||
name = side turn
|
||||
|
|
|
@ -157,6 +157,6 @@
|
|||
{TEST_FEEDING bob 1}
|
||||
{TEST_FEEDING bob 1}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -162,6 +162,6 @@
|
|||
{assert_test_false (side=4) (side=2)}
|
||||
{assert_test_false () (side=5)}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -116,6 +116,6 @@
|
|||
[/have_unit]
|
||||
[/not]
|
||||
)}
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
191
data/test/scenarios/interrupts.cfg
Normal file
191
data/test/scenarios/interrupts.cfg
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
{GENERIC_UNIT_TEST check_interrupts_break (
|
||||
[event]
|
||||
name=start
|
||||
{VARIABLE x 0}
|
||||
[while]
|
||||
[true][/true]
|
||||
[do]
|
||||
[if]
|
||||
{VARIABLE_CONDITIONAL x greater_than 5}
|
||||
[then]
|
||||
[break][/break]
|
||||
[/then]
|
||||
[/if]
|
||||
{VARIABLE_OP x add 1}
|
||||
[/do]
|
||||
[/while]
|
||||
{RETURN ({VARIABLE_CONDITIONAL x equals 6})}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_return (
|
||||
[event]
|
||||
name=start
|
||||
{VARIABLE x 0}
|
||||
[while]
|
||||
[true][/true]
|
||||
[do]
|
||||
[if]
|
||||
{VARIABLE_CONDITIONAL x greater_than 5}
|
||||
[then]
|
||||
[return][/return]
|
||||
[/then]
|
||||
[/if]
|
||||
{VARIABLE_OP x add 1}
|
||||
[/do]
|
||||
[/while]
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=start
|
||||
{RETURN ({VARIABLE_CONDITIONAL x equals 6})}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_continue (
|
||||
[event]
|
||||
name=start
|
||||
{VARIABLE x 0}
|
||||
[while]
|
||||
{VARIABLE_CONDITIONAL x less_than 5}
|
||||
[do]
|
||||
{VARIABLE_OP x add 5}
|
||||
[continue][/continue]
|
||||
{FAIL}
|
||||
[/do]
|
||||
[/while]
|
||||
{RETURN ({VARIABLE_CONDITIONAL x equals 5})}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_break_global (
|
||||
[event]
|
||||
name=start
|
||||
[break][/break]
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=start
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_continue_global (
|
||||
[event]
|
||||
name=start
|
||||
[lua]
|
||||
code=<<
|
||||
local H = wesnoth.require "lua/helper.lua"
|
||||
local A = H.set_wml_action_metatable{}
|
||||
local function continue()
|
||||
A.continue{}
|
||||
end
|
||||
-- Use pcall() to trap the WML error raised by continue in global scope
|
||||
local err, res = pcall(continue)
|
||||
if err then wesnoth.fire_event "success"
|
||||
else wesnoth.fire_event "fail" end
|
||||
>>
|
||||
[/lua]
|
||||
[/event]
|
||||
[event]
|
||||
name=success
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[event]
|
||||
name=fail
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_return_nested (
|
||||
[event]
|
||||
name=start
|
||||
[command]
|
||||
[return][/return]
|
||||
{FAIL}
|
||||
[/command]
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=start
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_elseif (
|
||||
[event]
|
||||
name=start
|
||||
{VARIABLE x 9}
|
||||
[if]
|
||||
{VARIABLE_CONDITIONAL x greater_than 10}
|
||||
[then]
|
||||
{FAIL}
|
||||
[/then]
|
||||
[elseif]
|
||||
{VARIABLE_CONDITIONAL x less_than 10}
|
||||
[then]
|
||||
[return][/return]
|
||||
[/then]
|
||||
[/elseif]
|
||||
[else]
|
||||
[wml_message]
|
||||
message="Reached the [else] block!"
|
||||
logger=error
|
||||
[/wml_message]
|
||||
{FAIL}
|
||||
[/else]
|
||||
[/if]
|
||||
[wml_message]
|
||||
message="Passed the [if] block!"
|
||||
logger=error
|
||||
[/wml_message]
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=start
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
{GENERIC_UNIT_TEST check_interrupts_case (
|
||||
[event]
|
||||
name=start
|
||||
{VARIABLE x 0}
|
||||
[switch]
|
||||
variable=x
|
||||
[case]
|
||||
value=1,3,5,7,9
|
||||
{FAIL}
|
||||
[/case]
|
||||
[case]
|
||||
value=0,2,4,6,8
|
||||
[return][/return]
|
||||
[/case]
|
||||
[case]
|
||||
value=0
|
||||
[wml_message]
|
||||
message="Reached next [case] block!"
|
||||
logger=error
|
||||
[/wml_message]
|
||||
{FAIL}
|
||||
[/case]
|
||||
[else]
|
||||
[wml_message]
|
||||
message="Reached the [else] block!"
|
||||
logger=error
|
||||
[/wml_message]
|
||||
{FAIL}
|
||||
[/else]
|
||||
[/switch]
|
||||
[wml_message]
|
||||
message="Passed the [switch] block!"
|
||||
logger=error
|
||||
[/wml_message]
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=start
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
|
@ -63,11 +63,11 @@
|
|||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 1
|
||||
{RETURN ([false][/false])}
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 42
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -80,11 +80,11 @@
|
|||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 1 refresh
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 42
|
||||
{RETURN ([false][/false])}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -102,15 +102,15 @@
|
|||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 1 end
|
||||
{RETURN ([false][/false])}
|
||||
{FAIL}
|
||||
[/event]
|
||||
[event]
|
||||
name=side 2 turn 42
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 43
|
||||
{RETURN ([false][/false])}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 2
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -114,7 +114,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name=side 1 turn 4 refresh
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -193,7 +193,7 @@ Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg"
|
|||
{RECRUIT_AND_CHECK 8 ("se") (7,4)}
|
||||
[event]
|
||||
name=side 1 turn 9 refresh
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
{RECRUIT_TEST recruit_facing_center (
|
||||
|
@ -210,6 +210,6 @@ Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg, Gg"
|
|||
{RECALL_AND_CHECK 8 ("nw") (7,4)}
|
||||
[event]
|
||||
name=side 1 turn 9 refresh
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
#If we got this far without triggering sighted, we fail the test.
|
||||
[event]
|
||||
name = side 2 turn 2
|
||||
{RETURN ([false][/false])}
|
||||
{FAIL}
|
||||
[/event]
|
||||
|
||||
#This makes the sides pass their turns, when the other events have taken place.
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name=side 2 turn 1
|
||||
{RETURN ([false][/false])}
|
||||
{FAIL}
|
||||
[/event]
|
||||
#enddef
|
||||
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
{ASSERT ({VARIABLE_CONDITIONAL b equals 2})}
|
||||
{ASSERT ({VARIABLE_CONDITIONAL c equals 3})}
|
||||
{ASSERT ({VARIABLE_CONDITIONAL d equals 4})}
|
||||
{RETURN [true][/true]}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
[/event]
|
||||
[event]
|
||||
name = side 2 turn 1
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
#If we got this far without failing an assertion, we pass the test.
|
||||
[event]
|
||||
name = side 2 turn 6
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
|
||||
#This makes all sides pass their turns, when the other events have taken place.
|
||||
|
@ -160,7 +160,7 @@
|
|||
[/time_area]
|
||||
{TEST_GRUNT_DAMAGE test3 1 6 "$(11*2)"}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[/test]
|
||||
|
||||
|
@ -210,6 +210,6 @@
|
|||
|
||||
{TEST_GRUNT_DAMAGE test3 1 6 "$(11*2)"}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[/test]
|
||||
|
|
|
@ -72,6 +72,6 @@
|
|||
[/and]
|
||||
[/not])}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
{ASSERT {VARIABLE_CONDITIONAL mx equals 3}}
|
||||
{ASSERT {VARIABLE_CONDITIONAL my equals 3}}
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
y={PY}
|
||||
[/move]
|
||||
[/do_command]
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
[/test]
|
||||
#enddef
|
||||
|
|
|
@ -111,6 +111,6 @@
|
|||
[/have_unit]
|
||||
)}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
[/have_unit]
|
||||
)}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -71,7 +71,7 @@
|
|||
[/have_unit]
|
||||
)}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -92,7 +92,7 @@
|
|||
[/have_unit]
|
||||
)}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -127,6 +127,6 @@
|
|||
[/have_unit]
|
||||
)}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -138,6 +138,6 @@
|
|||
[/do_command]
|
||||
{ASSERT_YES_9_5}
|
||||
|
||||
{RETURN ([true][/true])}
|
||||
{SUCCEED}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
[event]
|
||||
name= side turn end
|
||||
{RETURN [false][/false]}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -130,7 +130,7 @@
|
|||
|
||||
[event]
|
||||
name= side turn end
|
||||
{RETURN [false][/false]}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -159,7 +159,7 @@
|
|||
|
||||
[event]
|
||||
name= side turn end
|
||||
{RETURN [false][/false]}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -190,7 +190,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name= side 2 turn end
|
||||
{RETURN [false][/false]}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -221,7 +221,7 @@
|
|||
[/event]
|
||||
[event]
|
||||
name= side 2 turn end
|
||||
{RETURN [false][/false]}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
||||
|
@ -253,6 +253,6 @@
|
|||
[/event]
|
||||
[event]
|
||||
name= side 2 turn end
|
||||
{RETURN [false][/false]}
|
||||
{FAIL}
|
||||
[/event]
|
||||
)}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# By Elvish_Hunter, April 2014
|
||||
|
@ -10,31 +10,19 @@
|
|||
|
||||
# threading and subprocess are needed to run wmllint without freezing the window
|
||||
# codecs is used to save files as UTF8
|
||||
# Queue (queue in Python3) is needed to exchange informations between threads
|
||||
# queue is needed to exchange informations between threads
|
||||
# if we use the run_tool thread to do GUI stuff we obtain weird crashes
|
||||
# This happens because Tk is a single-thread GUI
|
||||
import sys,os,threading,subprocess,codecs
|
||||
|
||||
# sys.version_info checks the interpreter version
|
||||
# this is used to have a script that can run on both Python2 and Python3
|
||||
# not that useful until the mainline tools are updated, but still...
|
||||
if sys.version_info.major >= 3:
|
||||
import queue
|
||||
# tkinter modules
|
||||
from tkinter import *
|
||||
from tkinter.messagebox import *
|
||||
from tkinter.filedialog import *
|
||||
# ttk must be called last
|
||||
from tkinter.ttk import *
|
||||
else: # we are on Python 2
|
||||
import Queue
|
||||
# tkinter modules
|
||||
import tkFont as font # in Py3 it's tkinter.font
|
||||
from Tkinter import *
|
||||
from tkMessageBox import *
|
||||
from tkFileDialog import *
|
||||
# ttk must be called last
|
||||
from ttk import *
|
||||
import queue
|
||||
# tkinter modules
|
||||
from tkinter import *
|
||||
from tkinter.messagebox import *
|
||||
from tkinter.filedialog import *
|
||||
import tkinter.font as font
|
||||
# ttk must be called last
|
||||
from tkinter.ttk import *
|
||||
|
||||
# we need to know in what series we are
|
||||
# so set it in a constant and change it for every new series
|
||||
|
@ -87,10 +75,10 @@ def run_tool(tool,queue,command):
|
|||
queue.put_nowait(' '.join(command)+"\n")
|
||||
try:
|
||||
output=subprocess.check_output(command,stderr=subprocess.STDOUT,env=env)
|
||||
queue.put_nowait(output)
|
||||
queue.put_nowait(str(output, "utf8"))
|
||||
except subprocess.CalledProcessError as error:
|
||||
# post the precise message and the remaining output as a tuple
|
||||
queue.put_nowait((tool,error.returncode,error.output))
|
||||
queue.put_nowait((tool,error.returncode, str(error.output, "utf8")))
|
||||
|
||||
def is_wesnoth_tools_path(path):
|
||||
"""Checks if the supplied path may be a wesnoth/data/tools directory"""
|
||||
|
@ -142,7 +130,7 @@ class Tooltip(Toplevel):
|
|||
"""A tooltip, or balloon. Displays the specified help text when the
|
||||
mouse pointer stays on the widget for more than 500 ms."""
|
||||
# the master attribute retrieves the window where our "parent" widget is
|
||||
Toplevel.__init__(self,widget.master)
|
||||
super().__init__(widget.master)
|
||||
self.widget=widget
|
||||
self.preshow_id=None
|
||||
self.show_id=None
|
||||
|
@ -213,10 +201,7 @@ class Popup(Toplevel):
|
|||
"""Creates a popup that informs the user that the desired tool is running.
|
||||
Self destroys when the tool thread is over"""
|
||||
self.thread=thread
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent)
|
||||
else:
|
||||
Toplevel.__init__(self,parent)
|
||||
super().__init__(parent)
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
self.protocol("WM_DELETE_WINDOW",
|
||||
|
@ -262,10 +247,7 @@ class ContextMenu(Menu):
|
|||
def __init__(self,x,y,widget):
|
||||
"""A subclass of Menu, used to display a context menu in Text and Entry widgets
|
||||
If the widget isn't active, some options do not appear"""
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(None,tearoff=0) # otherwise Tk allows splitting it in a new window
|
||||
else:
|
||||
Menu.__init__(self,None,tearoff=0)
|
||||
super().__init__(None,tearoff=0) # otherwise Tk allows splitting it in a new window
|
||||
self.widget=widget
|
||||
# MacOS uses a key called Command, instead of the usual Control used by Windows and Linux
|
||||
# so prepare the accelerator strings accordingly
|
||||
|
@ -314,10 +296,7 @@ class EntryContext(Entry):
|
|||
def __init__(self,parent,**kwargs):
|
||||
"""An enhanced Entry widget that has a right-click menu
|
||||
Use like any other Entry widget"""
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent,**kwargs)
|
||||
else:
|
||||
Entry.__init__(self,parent,**kwargs)
|
||||
super().__init__(parent,**kwargs)
|
||||
attach_context_menu(self,self.on_context_menu)
|
||||
attach_select_all(self,self.on_select_all)
|
||||
def on_context_menu(self,event):
|
||||
|
@ -330,10 +309,7 @@ class SpinboxContext(Spinbox):
|
|||
def __init__(self,parent,**kwargs):
|
||||
"""An enhanced Spinbox widget that has a right-click menu
|
||||
Use like any other Spinbox widget"""
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent,**kwargs)
|
||||
else:
|
||||
Spinbox.__init__(self,parent,**kwargs)
|
||||
super().__init__(parent,**kwargs)
|
||||
attach_context_menu(self,self.on_context_menu)
|
||||
attach_select_all(self,self.on_select_all)
|
||||
def on_context_menu(self,event):
|
||||
|
@ -346,10 +322,7 @@ class EnhancedText(Text):
|
|||
def __init__(self,*args,**kwargs):
|
||||
"""A subclass of Text with a context menu
|
||||
Use it like any other Text widget"""
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(*args,**kwargs)
|
||||
else:
|
||||
Text.__init__(self,*args,**kwargs)
|
||||
super().__init__(*args,**kwargs)
|
||||
attach_context_menu(self,self.on_context_menu)
|
||||
attach_select_all(self,self.on_select_all)
|
||||
def on_context_menu(self,event):
|
||||
|
@ -363,10 +336,7 @@ class SelectDirectory(LabelFrame):
|
|||
def __init__(self,parent,textvariable=None,**kwargs):
|
||||
"""A subclass of LabelFrame sporting a readonly Entry and a Button with a folder icon.
|
||||
It comes complete with a context menu and a directory selection screen"""
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent,text="Directory",**kwargs)
|
||||
else:
|
||||
LabelFrame.__init__(self,parent,text="Directory",**kwargs)
|
||||
super().__init__(parent,text="Directory",**kwargs)
|
||||
self.textvariable=textvariable
|
||||
self.dir_entry=EntryContext(self,
|
||||
width=40,
|
||||
|
@ -440,10 +410,7 @@ class WmllintTab(Frame):
|
|||
def __init__(self,parent):
|
||||
# it means super(WmllintTab,self), that in turn means
|
||||
# Frame.__init__(self,parent)
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent)
|
||||
else:
|
||||
Frame.__init__(self,parent)
|
||||
super().__init__(parent)
|
||||
self.mode_variable=IntVar()
|
||||
self.mode_frame=LabelFrame(self,
|
||||
text="wmllint mode")
|
||||
|
@ -607,10 +574,7 @@ class WmllintTab(Frame):
|
|||
|
||||
class WmlscopeTab(Frame):
|
||||
def __init__(self,parent):
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent)
|
||||
else:
|
||||
Frame.__init__(self,parent)
|
||||
super().__init__(parent)
|
||||
self.options_frame=LabelFrame(self,
|
||||
text="wmlscope options")
|
||||
self.options_frame.grid(row=0,
|
||||
|
@ -817,10 +781,7 @@ class WmlscopeTab(Frame):
|
|||
|
||||
class WmlindentTab(Frame):
|
||||
def __init__(self,parent):
|
||||
if sys.version_info.major>=3:
|
||||
super().__init__(parent)
|
||||
else:
|
||||
Frame.__init__(self,parent)
|
||||
super().__init__(parent)
|
||||
self.mode_variable=IntVar()
|
||||
self.mode_frame=LabelFrame(self,
|
||||
text="wmlindent mode")
|
||||
|
@ -921,12 +882,8 @@ class WmlindentTab(Frame):
|
|||
class MainFrame(Frame):
|
||||
def __init__(self,parent):
|
||||
self.parent=parent
|
||||
if sys.version_info.major>=3:
|
||||
self.queue=queue.Queue()
|
||||
super().__init__(parent)
|
||||
else:
|
||||
self.queue=Queue.Queue()
|
||||
Frame.__init__(self,parent)
|
||||
self.queue=queue.Queue()
|
||||
super().__init__(parent)
|
||||
self.grid(sticky=N+E+S+W)
|
||||
self.buttonbox=Frame(self)
|
||||
self.buttonbox.grid(row=0,
|
||||
|
|
|
@ -56,7 +56,7 @@ Select the add-on you want to install from the list and click "OK". The download
|
|||
"asc.gif", "bg.gif", "desc.gif"]:
|
||||
Popen(["cp", "-u", am_dir + name, path])
|
||||
|
||||
campaigns = data.get_or_create_sub("campaigns")
|
||||
campaigns = data.get_all(tag = "campaigns")[0]
|
||||
w("<table class=\"tablesorter\" id=\"campaigns\">")
|
||||
w("<thead>")
|
||||
w("<tr>")
|
||||
|
@ -67,9 +67,9 @@ Select the add-on you want to install from the list and click "OK". The download
|
|||
w("<tbody>")
|
||||
root_dir = am_dir + "../../../"
|
||||
images_to_tc = []
|
||||
for campaign in campaigns.get_all("campaign"):
|
||||
for campaign in campaigns.get_all(tag = "campaign"):
|
||||
v = campaign.get_text_val
|
||||
translations = campaign.get_all("translation")
|
||||
translations = campaign.get_all(tag = "translation")
|
||||
languages = [x.get_text_val("language") for x in translations]
|
||||
w("<tr>")
|
||||
icon = v("icon", "")
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
import gzip, zlib, StringIO
|
||||
import gzip, zlib, io
|
||||
import socket, struct, glob, sys, shutil, threading, os, fnmatch
|
||||
import wesnoth.wmldata as wmldata
|
||||
import wesnoth.wmlparser as wmlparser
|
||||
import wesnoth.wmlparser3 as wmlparser
|
||||
|
||||
# See the following files (among others):
|
||||
# src/addon/*
|
||||
# src/network.cpp
|
||||
|
||||
def append_attributes(tag, **attributes):
|
||||
for k, v in attributes.items():
|
||||
if isinstance(k, str): k = k.encode("utf8")
|
||||
if isinstance(v, str): v = v.encode("utf8")
|
||||
kn = wmlparser.AttributeNode(k)
|
||||
vn = wmlparser.StringNode(v)
|
||||
kn.value.append(vn)
|
||||
tag.append(kn)
|
||||
|
||||
def append_tag(tag : wmlparser.TagNode, sub : str) -> wmlparser.TagNode:
|
||||
subtag = wmlparser.TagNode(sub.encode("utf8"))
|
||||
if tag:
|
||||
tag.append(subtag)
|
||||
return subtag
|
||||
|
||||
dumpi = 0
|
||||
class CampaignClient:
|
||||
# First port listed will be used as default.
|
||||
|
@ -95,20 +109,18 @@ class CampaignClient:
|
|||
|
||||
|
||||
def make_packet(self, doc):
|
||||
root = wmldata.DataSub("WML")
|
||||
root.insert(doc)
|
||||
return root.make_string()
|
||||
return doc.wml()
|
||||
|
||||
def send_packet(self, packet):
|
||||
"""
|
||||
Send binary data to the server.
|
||||
"""
|
||||
# Compress the packet before we send it
|
||||
io = StringIO.StringIO()
|
||||
z = gzip.GzipFile(mode = "w", fileobj = io)
|
||||
fio = io.BytesIO()
|
||||
z = gzip.GzipFile(mode = "wb", fileobj = fio)
|
||||
z.write(packet)
|
||||
z.close()
|
||||
zdata = io.getvalue()
|
||||
zdata = fio.getvalue()
|
||||
|
||||
zpacket = struct.pack("!I", len(zdata)) + zdata
|
||||
self.sock.sendall(zpacket)
|
||||
|
@ -117,7 +129,7 @@ class CampaignClient:
|
|||
"""
|
||||
Read binary data from the server.
|
||||
"""
|
||||
packet = ""
|
||||
packet = b""
|
||||
while len(packet) < 4 and not self.canceled:
|
||||
r = self.sock.recv(4 - len(packet))
|
||||
if not r:
|
||||
|
@ -131,7 +143,7 @@ class CampaignClient:
|
|||
if not self.quiet:
|
||||
sys.stderr.write("Receiving %d bytes.\n" % self.length)
|
||||
|
||||
packet = ""
|
||||
packet = b""
|
||||
while len(packet) < l and not self.canceled:
|
||||
r = self.sock.recv(l - len(packet))
|
||||
if not r:
|
||||
|
@ -144,16 +156,16 @@ class CampaignClient:
|
|||
if not self.quiet:
|
||||
sys.stderr.write("Received %d bytes.\n" % len(packet))
|
||||
|
||||
if packet.startswith("\x1F\x8B"):
|
||||
if packet.startswith(b"\x1F\x8B"):
|
||||
if self.verbose:
|
||||
sys.stderr.write("GZIP compression found...\n")
|
||||
io = StringIO.StringIO(packet)
|
||||
z = gzip.GzipFile(fileobj = io)
|
||||
bio = io.BytesIO(packet)
|
||||
z = gzip.GzipFile("rb", fileobj = bio)
|
||||
unzip = z.read()
|
||||
z.close()
|
||||
packet = unzip
|
||||
|
||||
elif packet.startswith( '\x78\x9C' ):
|
||||
elif packet.startswith(b'\x78\x9C' ):
|
||||
if self.verbose:
|
||||
sys.stderr.write("ZLIB compression found...\n")
|
||||
packet = zlib.decompres( packet )
|
||||
|
@ -169,77 +181,22 @@ class CampaignClient:
|
|||
|
||||
def unescape(self, data):
|
||||
# 01 is used as escape character
|
||||
data2 = ""
|
||||
data2 = b""
|
||||
escape = False
|
||||
for c in data:
|
||||
if escape:
|
||||
data2 += chr(ord(c) - 1)
|
||||
data2 += bytes([c - 1])
|
||||
escape = False
|
||||
elif c == "\01":
|
||||
elif c == 1:
|
||||
escape = True
|
||||
else:
|
||||
data2 += c
|
||||
data2 += bytes([c])
|
||||
return data2
|
||||
|
||||
def decode_WML(self, data):
|
||||
p = wmlparser.Parser( None, no_macros_in_string=True )
|
||||
p.verbose = False
|
||||
p.do_preprocessor_logic = True
|
||||
p.no_macros = True
|
||||
p.parse_text(data, binary=True)
|
||||
doc = wmldata.DataSub( "WML" )
|
||||
p.parse_top(doc)
|
||||
|
||||
return doc
|
||||
|
||||
def done():
|
||||
return pos[0] >= len(data)
|
||||
|
||||
def next():
|
||||
c = data[pos[0]]
|
||||
pos[0] += 1
|
||||
return c
|
||||
|
||||
def literal():
|
||||
s = pos[0]
|
||||
e = data.find("\00", s)
|
||||
|
||||
pack = data[s:e]
|
||||
|
||||
pack = pack.replace("\01\01", "\00")
|
||||
pack = pack.replace("\01\02", "\01")
|
||||
|
||||
pos[0] = e + 1
|
||||
|
||||
return pack
|
||||
|
||||
while not done():
|
||||
code = ord(next())
|
||||
if code == 0: # open element (name code follows)
|
||||
open_element = True
|
||||
elif code == 1: # close current element
|
||||
tag.pop()
|
||||
elif code == 2: # add code
|
||||
self.words[self.wordcount] = literal()
|
||||
self.wordcount += 1
|
||||
else:
|
||||
if code == 3:
|
||||
word = literal() # literal word
|
||||
else:
|
||||
word = self.words[code] # code
|
||||
if open_element: # we handle opening an element
|
||||
element = wmldata.DataSub(word)
|
||||
tag[-1].insert(element) # add it to the current one
|
||||
tag.append(element) # put to our stack to keep track
|
||||
elif word == "contents": # detect any binary attributes
|
||||
binary = wmldata.DataBinary(word, literal())
|
||||
tag[-1].insert(binary)
|
||||
else: # others are text attributes
|
||||
text = wmldata.DataText(word, literal())
|
||||
tag[-1].insert(text)
|
||||
open_element = False
|
||||
|
||||
return WML
|
||||
p = wmlparser.Parser()
|
||||
p.parse_binary(data)
|
||||
return p.root
|
||||
|
||||
def encode_WML( self, data ):
|
||||
"""
|
||||
|
@ -259,9 +216,9 @@ class CampaignClient:
|
|||
"""
|
||||
if self.error:
|
||||
return None
|
||||
request = wmldata.DataSub("request_campaign_list")
|
||||
request = append_tag(None, "request_campaign_list")
|
||||
if addon:
|
||||
request.insert_text("name", addon)
|
||||
append_attributes(request, name = addon)
|
||||
self.send_packet(self.make_packet(request))
|
||||
|
||||
return self.decode(self.read_packet())
|
||||
|
@ -270,9 +227,9 @@ class CampaignClient:
|
|||
"""
|
||||
Deletes the named campaign on the server.
|
||||
"""
|
||||
request = wmldata.DataSub("delete")
|
||||
request.set_text_val("name", name)
|
||||
request.set_text_val("passphrase", passphrase)
|
||||
request = append_tag(None, "delete")
|
||||
append_attributes(request, name = name,
|
||||
passphrase = passphrase)
|
||||
|
||||
self.send_packet(self.make_packet(request))
|
||||
return self.decode(self.read_packet())
|
||||
|
@ -281,10 +238,9 @@ class CampaignClient:
|
|||
"""
|
||||
Changes the passphrase of a campaign on the server.
|
||||
"""
|
||||
request = wmldata.DataSub("change_passphrase")
|
||||
request.set_text_val("name", name)
|
||||
request.set_text_val("passphrase", old)
|
||||
request.set_text_val("new_passphrase", new)
|
||||
request = append_tag(None, "change_passphrase")
|
||||
append_attributes(request, name = name,
|
||||
passphrase = old, new_passphrase = new)
|
||||
|
||||
self.send_packet(self.make_packet(request))
|
||||
return self.decode(self.read_packet())
|
||||
|
@ -293,8 +249,8 @@ class CampaignClient:
|
|||
"""
|
||||
Downloads the named campaign and returns it as a raw binary WML packet.
|
||||
"""
|
||||
request = wmldata.DataSub("request_campaign")
|
||||
request.insert(wmldata.DataText("name", name))
|
||||
request = append_tag(None, "request_campaign")
|
||||
append_attributes(request, name = name)
|
||||
self.send_packet(self.make_packet(request))
|
||||
raw_packet = self.read_packet()
|
||||
|
||||
|
@ -323,47 +279,45 @@ class CampaignClient:
|
|||
|
||||
The directory is the name of the campaign's directory.
|
||||
"""
|
||||
request = pbl.copy()
|
||||
request.name = "upload"
|
||||
request.set_text_val("name", name)
|
||||
|
||||
data = wmldata.DataSub("data")
|
||||
request.insert(data)
|
||||
request = pbl
|
||||
request.name = b"upload"
|
||||
append_attributes(request, name = name)
|
||||
data = append_tag(request, "data")
|
||||
|
||||
def put_file(name, f):
|
||||
for ig in ign:
|
||||
if ig and ig[-1] != "/" and fnmatch.fnmatch(name, ig):
|
||||
print("Ignored file", name)
|
||||
print(("Ignored file", name))
|
||||
return None
|
||||
fileNode = wmldata.DataSub("file")
|
||||
fileNode = append_tag(None, "file")
|
||||
|
||||
# Order in which we apply escape sequences matters.
|
||||
contents = f.read()
|
||||
contents = contents.replace("\x01", "\x01\x02" )
|
||||
contents = contents.replace("\x00", "\x01\x01")
|
||||
contents = contents.replace("\x0d", "\x01\x0e")
|
||||
contents = contents.replace("\xfe", "\x01\xff")
|
||||
contents = contents.replace(b"\x01", b"\x01\x02" )
|
||||
contents = contents.replace(b"\x00", b"\x01\x01")
|
||||
contents = contents.replace(b"\x0d", b"\x01\x0e")
|
||||
contents = contents.replace(b"\xfe", b"\x01\xff")
|
||||
|
||||
fileContents = wmldata.DataText("contents", contents)
|
||||
fileNode.insert(fileContents)
|
||||
fileNode.set_text_val("name", name)
|
||||
append_attributes(fileNode, name = name)
|
||||
append_attributes(fileNode, contents = contents)
|
||||
|
||||
return fileNode
|
||||
|
||||
def put_dir(name, path):
|
||||
for ig in ign:
|
||||
if ig and ig[-1] == "/" and fnmatch.fnmatch(name, ig[:-1]):
|
||||
print("Ignored dir", name)
|
||||
print(("Ignored dir", name))
|
||||
return None
|
||||
|
||||
dataNode = wmldata.DataSub("dir")
|
||||
dataNode.set_text_val("name", name)
|
||||
dataNode = append_tag("dir")
|
||||
append_attributes(dataNode, name = name)
|
||||
for fn in glob.glob(path + "/*"):
|
||||
if os.path.isdir(fn):
|
||||
sub = put_dir(os.path.basename(fn), fn)
|
||||
else:
|
||||
sub = put_file(os.path.basename(fn), open(fn, 'rb'))
|
||||
if sub: dataNode.insert(sub)
|
||||
if sub:
|
||||
dataNode.append(sub)
|
||||
return dataNode
|
||||
|
||||
# Only used if it's an old-style campaign directory
|
||||
|
@ -373,13 +327,16 @@ class CampaignClient:
|
|||
|
||||
if not self.quiet:
|
||||
sys.stderr.write("Adding directory %s as %s.\n" % (directory, name))
|
||||
data.insert(put_dir(name, directory))
|
||||
data.append(put_dir(name, directory))
|
||||
|
||||
packet = self.make_packet(request)
|
||||
open("packet.dump", "wb").write(packet)
|
||||
self.send_packet(packet)
|
||||
|
||||
return self.decode(self.read_packet())
|
||||
response = self.read_packet()
|
||||
if not response:
|
||||
response = b""
|
||||
return self.decode(response)
|
||||
|
||||
def get_campaign_raw_async(self, name):
|
||||
"""
|
||||
|
@ -450,20 +407,18 @@ class CampaignClient:
|
|||
os.mkdir(path)
|
||||
except OSError:
|
||||
pass
|
||||
for f in data.get_all("file"):
|
||||
for f in data.get_all(tag = "file"):
|
||||
name = f.get_text_val("name", "?")
|
||||
contents = f.get_text("contents")
|
||||
contents = f.get_binary("contents")
|
||||
if not contents:
|
||||
contents = ""
|
||||
contents = b""
|
||||
if not self.quiet:
|
||||
sys.stderr.write("File %s is empty.\n" % name)
|
||||
sys.stderr.write(f.debug(write = False) + "\n")
|
||||
if contents:
|
||||
contents = contents.get_value()
|
||||
if verbose:
|
||||
sys.stderr.write(i * " " + name + " (" +
|
||||
str(len(contents)) + ")\n")
|
||||
save = file( os.path.join(path, name), "wb")
|
||||
save = open( os.path.join(path, name), "wb")
|
||||
|
||||
# We MUST un-escape our data
|
||||
# Order we apply escape sequences matter here
|
||||
|
@ -471,7 +426,7 @@ class CampaignClient:
|
|||
save.write(contents)
|
||||
save.close()
|
||||
|
||||
for dir in data.get_all("dir"):
|
||||
for dir in data.get_all(tag = "dir"):
|
||||
name = dir.get_text_val("name", "?")
|
||||
shutil.rmtree(os.path.join(path, name), True)
|
||||
os.mkdir(os.path.join(path, name))
|
||||
|
|
|
@ -23,6 +23,14 @@ import sys, os.path, argparse, tempfile, shutil, logging, socket
|
|||
# in case the wesnoth python package has not been installed
|
||||
sys.path.append("data/tools")
|
||||
|
||||
print("""
|
||||
Note: campaignserver_client has since been moved to Python 3 - the
|
||||
easiest way to run this script is to use campaginserver_client from
|
||||
an earlier Wesnoth version. And then in the long run convert this
|
||||
script to Python 3 as well.
|
||||
""")
|
||||
sys.exit(1)
|
||||
|
||||
#import CampaignClient as libwml
|
||||
import wesnoth.campaignserver_client as libwml
|
||||
|
||||
|
|
498
data/tools/wesnoth/wmliterator3.py
Normal file
498
data/tools/wesnoth/wmliterator3.py
Normal file
|
@ -0,0 +1,498 @@
|
|||
"""
|
||||
wmliterator.py -- Python routines for navigating a Battle For Wesnoth WML tree
|
||||
Author: Sapient (Patrick Parker), 2007
|
||||
|
||||
Purpose:
|
||||
The WmlIterator class can be used to analyze and search the structure of WML
|
||||
files non-invasively (i.e. preserving existing line structure), and its main
|
||||
use is to determine when a transformation of deprecated content needs to take
|
||||
place. (I wrote it was because wmllint was trying to do a lot of things with
|
||||
regular expressions which really required a more algorithmic approach. Also,
|
||||
wmllint was often inconsistent with correct handling of comments and values
|
||||
inside strings.)
|
||||
|
||||
Limitations:
|
||||
The WmlIterator does not attempt to expand macros, only to recognize them as
|
||||
another level of nesting. Also, the handling of multiple assignment syntax
|
||||
is somewhat limited (for similar reasons). Adding seamless support for these
|
||||
would be ideal, but it presents a design challenge since the iteration is
|
||||
supposed to be non-invasive. Thus, the current behavior is considered good
|
||||
enough for now.
|
||||
"""
|
||||
|
||||
from functools import total_ordering
|
||||
import sys, re, copy, codecs
|
||||
keyPattern = re.compile('(\w+)(,\s?\w+)*\s*=')
|
||||
keySplit = re.compile(r'[=,\s]')
|
||||
tagPattern = re.compile(r'(^|(?<![\w|}]))(\[/?\+?[a-z _]+\])')
|
||||
macroOpenPattern = re.compile(r'(\{[^\s\}\{]*)')
|
||||
macroClosePattern = re.compile(r'\}')
|
||||
closeMacroType = 'end of macro'
|
||||
|
||||
silenceErrors = {}
|
||||
|
||||
def wmlfind(element, wmlItor):
|
||||
"""Find a simple element from traversing a WML iterator"""
|
||||
for itor in wmlItor.copy():
|
||||
if element == itor.element:
|
||||
return itor
|
||||
return None
|
||||
|
||||
def wmlfindin(element, scopeElement, wmlItor):
|
||||
"""Find an element inside a particular type of scope element"""
|
||||
for itor in wmlItor.copy():
|
||||
if element == itor.element:
|
||||
if itor.scopes:
|
||||
if scopeElement == itor.scopes[-1].element:
|
||||
return itor
|
||||
elif not scopeElement:
|
||||
# allow searching in the empty scope
|
||||
return itor
|
||||
return None
|
||||
|
||||
|
||||
def isDirective(elem):
|
||||
"Identify things that shouldn't be indented."
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
return elem.startswith(("#ifdef", "#ifndef", "#ifhave", "#ifnhave", "#ifver", "#ifnver", "#else", "#endif", "#define", "#enddef", "#undef"))
|
||||
|
||||
|
||||
def isCloser(elem):
|
||||
"Are we looking at a closing tag?"
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
return type(elem) == type("") and elem.startswith("[/")
|
||||
|
||||
def isMacroCloser(elem):
|
||||
"Are we looking at a macro closer?"
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
return type(elem) == type("") and elem == closeMacroType
|
||||
|
||||
def isOpener(elem):
|
||||
"Are we looking at an opening tag?"
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
return type(elem) == type("") and elem.startswith("[") and not isCloser(elem)
|
||||
|
||||
def isExtender(elem):
|
||||
"Are we looking at an extender tag?"
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
return type(elem) == type("") and elem.startswith("[+")
|
||||
|
||||
def isMacroOpener(elem):
|
||||
"Are we looking at a macro opener?"
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
return type(elem) == type("") and elem.startswith("{")
|
||||
|
||||
def isAttribute(elem):
|
||||
"Are we looking at an attribute (or attribute tuple)?"
|
||||
if isinstance(elem, WmlIterator):
|
||||
elem = elem.element
|
||||
if type(elem) == type(()):
|
||||
elem = elem[0]
|
||||
return type(elem) == type("") and elem.endswith("=")
|
||||
|
||||
# the total_ordering decorator from functools allows to define only two comparison
|
||||
# methods, and Python generates the remaining methods
|
||||
# it comes with a speed penalty, but the alternative is defining six methods by hand...
|
||||
@total_ordering
|
||||
class WmlIterator(object):
|
||||
"""Return an iterable WML navigation object.
|
||||
Initialize with a list of lines or a file; if the the line list is
|
||||
empty and the filename is specified, lines will be read from the file.
|
||||
|
||||
Note: if changes are made to lines while iterating, this may produce
|
||||
unexpected results. In such case, seek() to the line number of a
|
||||
scope behind where changes were made.
|
||||
Important Attributes:
|
||||
lines - this is an internal list of all the physical lines
|
||||
scopes - this is an internal list of all open scopes (as iterators)
|
||||
note: when retrieving an iterator from this list, always
|
||||
use a copy to perform seek() or next(), and not the original
|
||||
element - the wml tag, key, or macro name for this logical line
|
||||
(in complex cases, this may be a tuple of elements...
|
||||
see parseElements for list of possible values)
|
||||
text - the exact text of this logical line, as it appears in the
|
||||
original source, and ending with a newline
|
||||
note: the logical line also includes multi-line quoted strings
|
||||
span - the number of physical lines in this logical line:
|
||||
always 1, unless text contains a multi-line quoted string
|
||||
lineno - a zero-based line index marking where this text begins
|
||||
"""
|
||||
def __init__(self, lines=None, filename=None, begin=-1):
|
||||
"Initialize a new WmlIterator."
|
||||
self.fname = filename
|
||||
if lines is None:
|
||||
lines = []
|
||||
if filename:
|
||||
try:
|
||||
with codecs.open(self.fname, "r", "utf8") as ifp:
|
||||
lines = ifp.readlines()
|
||||
except Exception:
|
||||
self.printError('error opening file')
|
||||
self.lines = lines
|
||||
self.reset()
|
||||
self.seek(begin)
|
||||
|
||||
def parseQuotes(self, lines):
|
||||
"""Return the line or multiline text if a quote spans multiple lines"""
|
||||
text = lines[self.lineno]
|
||||
span = 1
|
||||
begincomment = text.find('#')
|
||||
if begincomment < 0:
|
||||
begincomment = None
|
||||
beginquote = text[:begincomment].find('<<')
|
||||
while beginquote >= 0:
|
||||
endquote = -1
|
||||
beginofend = beginquote+2
|
||||
while endquote < 0:
|
||||
endquote = text.find('>>', beginofend)
|
||||
if endquote < 0:
|
||||
if self.lineno + span >= len(lines):
|
||||
self.printError('reached EOF due to unterminated string at line', self.lineno+1)
|
||||
return text, span
|
||||
beginofend = len(text)
|
||||
text += lines[self.lineno + span]
|
||||
span += 1
|
||||
begincomment = text.find('#', endquote+2)
|
||||
if begincomment < 0:
|
||||
begincomment = None
|
||||
beginquote = text[:begincomment].find('<<', endquote+2)
|
||||
beginquote = text[:begincomment].find('"')
|
||||
while beginquote >= 0:
|
||||
endquote = -1
|
||||
beginofend = beginquote+1
|
||||
while endquote < 0:
|
||||
endquote = text.find('"', beginofend)
|
||||
if endquote < 0:
|
||||
if self.lineno + span >= len(lines):
|
||||
self.printError('reached EOF due to unterminated string at line', self.lineno+1)
|
||||
return text, span
|
||||
beginofend = len(text)
|
||||
text += lines[self.lineno + span]
|
||||
span += 1
|
||||
begincomment = text.find('#', endquote+1)
|
||||
if begincomment < 0:
|
||||
begincomment = None
|
||||
beginquote = text[:begincomment].find('"', endquote+1)
|
||||
return text, span
|
||||
|
||||
def closeScope(self, scopes, closerElement):
|
||||
"""Close the most recently opened scope. Return false if not enough scopes.
|
||||
note: directives close all the way back to the last open directive
|
||||
non-directives cannot close a directive and will no-op in that case."""
|
||||
try:
|
||||
if isDirective(closerElement):
|
||||
while not isDirective(scopes.pop()):
|
||||
pass
|
||||
elif (closerElement==closeMacroType):
|
||||
elem = ''
|
||||
while not elem.startswith('{'):
|
||||
closed = scopes.pop()
|
||||
elem = closed
|
||||
if isinstance(closed, WmlIterator):
|
||||
elem = closed.element
|
||||
if isDirective(elem):
|
||||
self.printScopeError(closerElement)
|
||||
scopes.append(closed) # to reduce additional errors (hopefully)
|
||||
return True
|
||||
elif not isDirective(scopes[-1]):
|
||||
closed = scopes.pop()
|
||||
elem = closed
|
||||
if isinstance(closed, WmlIterator):
|
||||
elem = closed.element
|
||||
if (elem.startswith('{') and closerElement != closeMacroType):
|
||||
scopes.append(closed)
|
||||
elif (isOpener(elem) and closerElement != '[/'+elem[1:]
|
||||
and '+'+closerElement != elem[1]+'[/'+elem[2:]):
|
||||
self.printError('reached', closerElement, 'at line', self.lineno+1, 'before closing scope', elem)
|
||||
scopes.append(closed) # to reduce additional errors (hopefully)
|
||||
return True
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
def parseElements(self, text):
|
||||
"""Remove any closed scopes, return a list of element names
|
||||
and list of new unclosed scopes
|
||||
Element Types:
|
||||
tags: one of "[tag_name]" or "[/tag_name]"
|
||||
[tag_name] - opens a scope
|
||||
[/tag_name] - closes a scope
|
||||
keys: either "key=" or ("key1=", "key2=") for multi-assignment
|
||||
key= - does not affect the scope
|
||||
key1,key2= - multi-assignment returns multiple elements
|
||||
directives: one of "#ifdef", "#ifndef", "#ifhave", "#ifnhave", "#ifver", "#ifnver", "#else", "#endif", "#define", "#enddef"
|
||||
#ifdef - opens a scope
|
||||
#ifndef - opens a scope
|
||||
#ifhave - opens a scope
|
||||
#ifnhave - opens a scope
|
||||
#ifver - opens a scope
|
||||
#ifnver - opens a scope
|
||||
#else - closes a scope, also opens a new scope
|
||||
#endif - closes a scope
|
||||
#define - opens a scope
|
||||
#enddef - closes a scope
|
||||
macro calls: "{MACRO_NAME}"
|
||||
{MACRO_NAME - opens a scope
|
||||
} - closes a scope
|
||||
"""
|
||||
elements = [] #(elementType, sortPos, scopeDelta)
|
||||
# first remove any lua strings
|
||||
beginquote = text.find('<<')
|
||||
while beginquote >= 0:
|
||||
endquote = text.find('>>', beginquote+2)
|
||||
if endquote < 0:
|
||||
text = text[:beginquote]
|
||||
beginquote = -1 #terminate loop
|
||||
else:
|
||||
text = text[:beginquote] + text[endquote+2:]
|
||||
beginquote = text.find('<<')
|
||||
# remove any quoted strings
|
||||
beginquote = text.find('"')
|
||||
while beginquote >= 0:
|
||||
endquote = text.find('"', beginquote+1)
|
||||
if endquote < 0:
|
||||
text = text[:beginquote]
|
||||
beginquote = -1 #terminate loop
|
||||
else:
|
||||
text = text[:beginquote] + text[endquote+1:]
|
||||
beginquote = text.find('"')
|
||||
# next remove any comments
|
||||
text = text.lstrip()
|
||||
commentSearch = 1
|
||||
if text.startswith('#ifdef'):
|
||||
return (['#ifdef'],)*2
|
||||
elif text.startswith('#ifndef'):
|
||||
return (['#ifndef'],)*2
|
||||
elif text.startswith('#ifhave'):
|
||||
return (['#ifhave'],)*2
|
||||
elif text.startswith('#ifnhave'):
|
||||
return (['#ifnhave'],)*2
|
||||
elif text.startswith('#ifver'):
|
||||
return (['#ifver'],)*2
|
||||
elif text.startswith('#ifnver'):
|
||||
return (['#ifnver'],)*2
|
||||
elif text.startswith('#else'):
|
||||
if not self.closeScope(self.scopes, '#else'):
|
||||
self.printScopeError('#else')
|
||||
return (['#else'],)*2
|
||||
elif text.startswith('#endif'):
|
||||
if not self.closeScope(self.scopes, '#endif'):
|
||||
self.printScopeError('#endif')
|
||||
return ['#endif'], []
|
||||
elif text.startswith('#define'):
|
||||
return (['#define'],)*2
|
||||
elif text.find('#enddef') >= 0:
|
||||
elements.append(('#enddef', text.find('#enddef'), -1))
|
||||
elif text.startswith('#po:') or text.startswith('# po:'):
|
||||
elements.append(("#po", 0, 0))
|
||||
else:
|
||||
commentSearch = 0
|
||||
begincomment = text.find('#', commentSearch)
|
||||
if begincomment >= 0:
|
||||
text = text[:begincomment]
|
||||
#now find elements in a loop
|
||||
for m in tagPattern.finditer(text):
|
||||
delta = 1
|
||||
if isCloser(m.group(2)):
|
||||
delta = -1
|
||||
elements.append((m.group(2), m.start(), delta))
|
||||
for m in keyPattern.finditer(text):
|
||||
for i, k in enumerate(keySplit.split(m.group(0))):
|
||||
if k:
|
||||
elements.append((k+'=', m.start()+i, 0))
|
||||
for m in macroOpenPattern.finditer(text):
|
||||
elements.append((m.group(1), m.start(), 1))
|
||||
for m in macroClosePattern.finditer(text):
|
||||
elements.append((closeMacroType, m.start(), -1))
|
||||
#sort by start position
|
||||
elements.sort(key=lambda x:x[1])
|
||||
resultElements = []
|
||||
openedScopes = []
|
||||
for elem, sortPos, scopeDelta in elements:
|
||||
while scopeDelta < 0:
|
||||
if not(self.closeScope(openedScopes, elem)\
|
||||
or self.closeScope(self.scopes, elem)):
|
||||
self.printScopeError(elem)
|
||||
scopeDelta += 1
|
||||
while scopeDelta > 0:
|
||||
openedScopes.append(elem)
|
||||
scopeDelta -= 1
|
||||
resultElements.append(elem)
|
||||
return resultElements, openedScopes
|
||||
|
||||
def printScopeError(self, elementType):
|
||||
"""Print out warning if a scope was unable to close"""
|
||||
self.printError('attempt to close empty scope at', elementType, 'line', self.lineno+1)
|
||||
|
||||
def __iter__(self):
|
||||
"""The magic iterator method"""
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.fname, self.lineno, self.element) == \
|
||||
(other.fname, other.lineno, other.element)
|
||||
|
||||
def __gt__(self, other):
|
||||
return (self.fname, self.lineno, self.element) > \
|
||||
(other.fname, other.lineno, other.element)
|
||||
|
||||
def reset(self):
|
||||
"""Reset any line tracking information to defaults"""
|
||||
self.lineno = -1
|
||||
self.scopes = []
|
||||
self.nextScopes = []
|
||||
self.text = ""
|
||||
self.span = 1
|
||||
self.element = ""
|
||||
return self
|
||||
|
||||
def seek(self, lineno, clearEnd=True):
|
||||
"""Move the iterator to a specific line number"""
|
||||
if clearEnd:
|
||||
self.endScope = None
|
||||
if lineno < self.lineno:
|
||||
for scope in reversed(self.scopes):
|
||||
# if moving backwards, try to re-use a scope iterator
|
||||
if scope.lineno <= lineno:
|
||||
# copy the scope iterator's state to self
|
||||
self.__dict__ = dict(scope.__dict__)
|
||||
self.scopes = scope.scopes[:]
|
||||
self.nextScopes = scope.nextScopes[:]
|
||||
break
|
||||
else:
|
||||
# moving backwards past all scopes forces a reset
|
||||
self.reset()
|
||||
while self.lineno + self.span - 1 < lineno:
|
||||
next(self)
|
||||
return self
|
||||
|
||||
def ancestors(self):
|
||||
"""Return a list of tags enclosing this location, outermost first."""
|
||||
return tuple([x.element for x in self.scopes])
|
||||
|
||||
def hasNext(self):
|
||||
"""Some loops may wish to check this method instead of calling next()
|
||||
and handling StopIteration... note: inaccurate for ScopeIterators"""
|
||||
return len(self.lines) > self.lineno + self.span
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of this iterator"""
|
||||
itor = copy.copy(self)
|
||||
itor.scopes = self.scopes[:]
|
||||
itor.nextScopes = self.nextScopes[:]
|
||||
return itor
|
||||
|
||||
def __str__(self):
|
||||
"""Return a pretty string representation"""
|
||||
if self.lineno == -1:
|
||||
return 'beginning of file'
|
||||
loc = ' at line ' + str(self.lineno+1)
|
||||
if self.element:
|
||||
return str(self.element) + loc
|
||||
if self.text.strip():
|
||||
return 'text' + loc
|
||||
return 'whitespace' + loc
|
||||
|
||||
def __repr__(self):
|
||||
"""Return a very basic string representation"""
|
||||
return 'WmlIterator<' + repr(self.element) +', line %d>'%(self.lineno+1)
|
||||
|
||||
def __next__(self):
|
||||
"""Move the iterator to the next line number
|
||||
note: May raise StopIteration"""
|
||||
if not self.hasNext():
|
||||
if self.scopes:
|
||||
self.printError("reached EOF with open scopes", self.scopes)
|
||||
raise StopIteration
|
||||
self.lineno = self.lineno + self.span
|
||||
self.text, self.span = self.parseQuotes(self.lines)
|
||||
self.scopes.extend(self.nextScopes)
|
||||
self.element, nextScopes = self.parseElements(self.text)
|
||||
self.nextScopes = []
|
||||
for elem in nextScopes:
|
||||
# remember scopes by storing a copy of the iterator
|
||||
copyItor = self.copy()
|
||||
copyItor.element = elem
|
||||
self.nextScopes.append(copyItor)
|
||||
copyItor.nextScopes.append(copyItor)
|
||||
if(len(self.element) == 1):
|
||||
# currently we only wish to handle simple single assignment syntax
|
||||
self.element = self.element[0]
|
||||
if self.endScope is not None and not self.scopes.count(self.endScope):
|
||||
raise StopIteration
|
||||
return self
|
||||
|
||||
def isOpener(self):
|
||||
return isOpener(self)
|
||||
|
||||
def isCloser(self):
|
||||
return isCloser(self)
|
||||
|
||||
def isExtender(self):
|
||||
return isExtender(self)
|
||||
|
||||
def isMacroOpener(self):
|
||||
return isMacroOpener(self)
|
||||
|
||||
def isMacroCloser(self):
|
||||
return isMacroCloser(self)
|
||||
|
||||
def isAttribute(self):
|
||||
return isAttribute(self)
|
||||
|
||||
def iterScope(self):
|
||||
"""Return an iterator for the current scope"""
|
||||
if not self.scopes:
|
||||
return WmlIterator(self.lines, self.fname)
|
||||
scopeItor = self.scopes[-1].copy()
|
||||
scopeItor.endScope = self.scopes[-1]
|
||||
return scopeItor
|
||||
|
||||
def printError(nav, *misc):
|
||||
"""Print error associated with a given file; avoid printing duplicates"""
|
||||
if nav.fname:
|
||||
silenceValue = ' '.join(map(str, misc))
|
||||
if nav.fname not in silenceErrors:
|
||||
print(nav.fname, file=sys.stderr)
|
||||
silenceErrors[nav.fname] = set()
|
||||
elif silenceValue in silenceErrors[nav.fname]:
|
||||
return # do not print a duplicate error for this file
|
||||
silenceErrors[nav.fname].add(silenceValue)
|
||||
print('wmliterator:', end=" ", file=sys.stderr)
|
||||
for item in misc:
|
||||
print(item, end=" ", file=sys.stderr)
|
||||
print("", file=sys.stderr) #terminate line
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""Perform a test run on a file or directory"""
|
||||
import os, glob
|
||||
didSomething = False
|
||||
flist = sys.argv[1:]
|
||||
if not flist:
|
||||
print('Current directory is', os.getcwd())
|
||||
flist = glob.glob(os.path.join(os.getcwd(), input('Which file(s) would you like to test?\n')))
|
||||
while flist:
|
||||
fname = flist.pop()
|
||||
if os.path.isdir(fname):
|
||||
flist += glob.glob(fname + os.path.sep + '*')
|
||||
continue
|
||||
if not os.path.isfile(fname) or os.path.splitext(fname)[1] != '.cfg':
|
||||
continue
|
||||
print('Reading', fname+'...')
|
||||
didSomething = True
|
||||
with codecs.open(fname, "r", "utf8") as f:
|
||||
itor = WmlIterator(f.readlines())
|
||||
for i in itor:
|
||||
pass
|
||||
print(itor.lineno + itor.span, 'lines read.')
|
||||
if not didSomething:
|
||||
print('That is not a valid .cfg file')
|
||||
if os.name == 'nt' and os.path.splitext(__file__)[0].endswith('wmliterator') and not sys.argv[1:]:
|
||||
os.system('pause')
|
||||
|
||||
# wmliterator.py ends here
|
974
data/tools/wesnoth/wmltools3.py
Normal file
974
data/tools/wesnoth/wmltools3.py
Normal file
|
@ -0,0 +1,974 @@
|
|||
"""
|
||||
wmltools.py -- Python routines for working with a Battle For Wesnoth WML tree
|
||||
|
||||
"""
|
||||
|
||||
from functools import total_ordering
|
||||
import collections, codecs
|
||||
import sys, os, re, sre_constants, hashlib, glob, gzip
|
||||
import string
|
||||
|
||||
map_extensions = ("map", "mask")
|
||||
image_extensions = ("png", "jpg", "jpeg")
|
||||
sound_extensions = ("ogg", "wav")
|
||||
vc_directories = (".git", ".svn")
|
||||
l10n_directories = ("l10n",)
|
||||
resource_extensions = map_extensions + image_extensions + sound_extensions
|
||||
image_reference = r"[A-Za-z0-9{}.][A-Za-z0-9_/+{}.-]*\.(png|jpe?g)(?=(~.*)?)"
|
||||
|
||||
def is_root(dirname):
|
||||
"Is the specified path the filesystem root?"
|
||||
return dirname == os.sep or (os.sep == '\\' and dirname.endswith(':\\'))
|
||||
|
||||
def pop_to_top(whoami):
|
||||
"Pop upward to the top-level directory."
|
||||
upwards = os.getcwd().split(os.sep)
|
||||
upwards.reverse()
|
||||
for pathpart in upwards:
|
||||
# Loose match because people have things like git trees.
|
||||
if os.path.basename(pathpart).find("wesnoth") > -1:
|
||||
break
|
||||
else:
|
||||
os.chdir("..")
|
||||
else:
|
||||
print(whoami + ": must be run from within a Battle "
|
||||
"for Wesnoth source tree.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def string_strip(value):
|
||||
"String-strip the value"
|
||||
if value.startswith('"'):
|
||||
value = value[1:]
|
||||
if value.endswith('"'):
|
||||
value = value[:-1]
|
||||
if value.startswith("'"):
|
||||
value = value[1:]
|
||||
if value.endswith("'"):
|
||||
value = value[:-1]
|
||||
return value
|
||||
|
||||
def attr_strip(value):
|
||||
"Strip away an (optional) translation mark and string quotes."
|
||||
value = value.strip()
|
||||
if value.startswith('_'):
|
||||
value = value[1:]
|
||||
value = value.strip()
|
||||
return string_strip(value)
|
||||
|
||||
def comma_split(csstring, list=None, strip="r"):
|
||||
"Split a comma-separated string, and append the entries to a list if specified."
|
||||
vallist = [x.lstrip() for x in csstring.split(",") if x.lstrip()]
|
||||
# strip=: utils::split will remove trailing whitespace from items in comma-
|
||||
# separated lists but the wml-tags.lua split function only removes leading
|
||||
# whitespace. So two flags are offered to change default behavior: one to
|
||||
# lstrip() only, the other to warn about trailing whitespace.
|
||||
if 'w' in strip:
|
||||
for item in vallist:
|
||||
if re.search('\s$', item):
|
||||
print('Trailing whitespace may be problematic: "%s" in "%s"' % (item, csstring))
|
||||
if 'l' not in strip:
|
||||
vallist = [x.rstrip() for x in vallist]
|
||||
if list is not None:
|
||||
list.extend(vallist)
|
||||
else:
|
||||
return vallist
|
||||
|
||||
def parse_attribute(line):
|
||||
"Parse a WML key-value pair from a line."
|
||||
if '=' not in line or line.find("#") > -1 and line.find("#") < line.find("="):
|
||||
return None
|
||||
where = line.find("=")
|
||||
leader = line[:where]
|
||||
after = line[where+1:]
|
||||
after = after.lstrip()
|
||||
if re.search("\s#", after):
|
||||
where = len(re.split("\s+#", after)[0])
|
||||
value = after[:where]
|
||||
comment = after[where:]
|
||||
else:
|
||||
value = after.rstrip()
|
||||
comment = ""
|
||||
# Return four fields: stripped key, part of line before value,
|
||||
# value, trailing whitespace and comment.
|
||||
return (leader.strip(), leader+"=", string_strip(value), comment)
|
||||
|
||||
class Forest:
|
||||
"Return an iterable directory forest object."
|
||||
def __init__(self, dirpath, exclude=None):
|
||||
"Get the names of all files under dirpath, ignoring version-control directories."
|
||||
self.forest = []
|
||||
self.dirpath = dirpath
|
||||
roots = ["campaigns", "add-ons"]
|
||||
for directory in dirpath:
|
||||
subtree = []
|
||||
rooted = False
|
||||
if os.path.isdir(directory): # So we skip .cfgs in a UMC mirror
|
||||
oldmain = os.path.join(os.path.dirname(directory), os.path.basename(directory) + '.cfg')
|
||||
if os.path.isfile(oldmain):
|
||||
subtree.append(oldmain)
|
||||
base = os.path.basename(os.path.dirname(os.path.abspath(directory)))
|
||||
if base in roots or base == "core":
|
||||
rooted = True
|
||||
for root, dirs, files in os.walk(directory):
|
||||
dirs.sort()
|
||||
dirlist = [x for x in dirs]
|
||||
# Split out individual campaigns/add-ons into their own subtrees
|
||||
if not rooted:
|
||||
if os.path.basename(root) == "core":
|
||||
rooted = True
|
||||
elif os.path.basename(root) in roots:
|
||||
for subdir in dirlist:
|
||||
if subdir + '.cfg' in files:
|
||||
files.remove(subdir + '.cfg')
|
||||
dirs.remove(subdir)
|
||||
dirpath.append(os.path.join(root, subdir))
|
||||
rooted = True
|
||||
elif "_info.cfg" in files or "info.cfg" in files:
|
||||
rooted = True
|
||||
roots.append(os.path.basename(os.path.dirname(os.path.abspath(root))))
|
||||
else:
|
||||
stop = min(len(dirs), 5)
|
||||
count = 0
|
||||
for subdir in dirlist[:stop]:
|
||||
if os.path.isfile(os.path.join(root, subdir, '_info.cfg')):
|
||||
count += 1
|
||||
elif os.path.isfile(os.path.join(root, subdir, 'info.cfg')):
|
||||
if os.path.isfile(os.path.join(root, subdir, 'COPYING.txt')):
|
||||
count += 1
|
||||
if count >= (stop // 2):
|
||||
roots.append(os.path.basename(root))
|
||||
for subdir in dirlist:
|
||||
if subdir + '.cfg' in files:
|
||||
files.remove(subdir + '.cfg')
|
||||
dirs.remove(subdir)
|
||||
dirpath.append(os.path.join(root, subdir))
|
||||
subtree.extend([os.path.normpath(os.path.join(root, x)) for x in files])
|
||||
# Always look at _main.cfg first
|
||||
maincfgs = [elem for elem in subtree if elem.endswith("_main.cfg")]
|
||||
rest = [elem for elem in subtree if not elem.endswith("_main.cfg")]
|
||||
subtree = sorted(maincfgs) + sorted(rest)
|
||||
self.forest.append(subtree)
|
||||
for i in self.forest:
|
||||
# Ignore version-control subdirectories and Emacs tempfiles
|
||||
for dirkind in vc_directories + l10n_directories:
|
||||
i = [x for x in i if dirkind not in x]
|
||||
i = [x for x in i if '.#' not in x]
|
||||
i = [x for x in i if not os.path.isdir(x)]
|
||||
if exclude:
|
||||
i = [x for x in i if not re.search(exclude, x)]
|
||||
i = [x for x in i if not x.endswith("-bak")]
|
||||
# Compute cliques (will be used later for visibility checks)
|
||||
self.clique = {}
|
||||
counter = 0
|
||||
for tree in self.forest:
|
||||
for filename in tree:
|
||||
self.clique[filename] = counter
|
||||
counter += 1
|
||||
def parent(self, filename):
|
||||
"Return the directory root that caused this path to be included."
|
||||
return self.dirpath[self.clique[filename]]
|
||||
def neighbors(self, fn1, fn2):
|
||||
"Are two files from the same tree?"
|
||||
return self.clique[fn1] == self.clique[fn2]
|
||||
def flatten(self):
|
||||
"Return a flattened list of all files in the forest."
|
||||
allfiles = []
|
||||
for tree in self.forest:
|
||||
allfiles += tree
|
||||
return allfiles
|
||||
def generator(self):
|
||||
"Return a generator that walks through all files."
|
||||
for (directory, tree) in zip(self.dirpath, self.forest):
|
||||
for filename in tree:
|
||||
yield (directory, filename)
|
||||
|
||||
def iswml(filename):
|
||||
"Is the specified filename WML?"
|
||||
return filename.endswith(".cfg")
|
||||
|
||||
def issave(filename):
|
||||
"Is the specified filename a WML save? (Detects compressed saves too.)"
|
||||
if isresource(filename):
|
||||
return False
|
||||
if filename.endswith(".gz"):
|
||||
with gzip.open(filename) as content:
|
||||
firstline = content.readline()
|
||||
else:
|
||||
try:
|
||||
with codecs.open(filename, "r", "utf8") as content:
|
||||
firstline = content.readline()
|
||||
except UnicodeDecodeError:
|
||||
# our saves are in UTF-8, so this file shouldn't be one
|
||||
return False
|
||||
return firstline.startswith("label=")
|
||||
|
||||
def isresource(filename):
|
||||
"Is the specified name a resource?"
|
||||
(root, ext) = os.path.splitext(filename)
|
||||
return ext and ext[1:] in resource_extensions
|
||||
|
||||
def parse_macroref(start, line):
|
||||
brackdepth = parendepth = 0
|
||||
instring = False
|
||||
args = []
|
||||
arg = ""
|
||||
for i in range(start, len(line)):
|
||||
if instring:
|
||||
if line[i] == '"':
|
||||
instring = False
|
||||
arg += line[i]
|
||||
elif line[i] == '"':
|
||||
instring = not instring
|
||||
arg += line[i]
|
||||
elif line[i] == "{":
|
||||
if brackdepth > 0:
|
||||
arg += line[i]
|
||||
brackdepth += 1
|
||||
elif line[i] == "}":
|
||||
brackdepth -= 1
|
||||
if brackdepth == 0:
|
||||
if not line[i-1].isspace():
|
||||
arg = arg.strip()
|
||||
if arg.startswith('"') and arg.endswith('"'):
|
||||
arg = arg[1:-1].strip()
|
||||
args.append(arg)
|
||||
arg = ""
|
||||
break
|
||||
else:
|
||||
arg += line[i]
|
||||
elif line[i] == "(":
|
||||
parendepth += 1
|
||||
elif line[i] == ")":
|
||||
parendepth -= 1
|
||||
elif not line[i-1].isspace() and \
|
||||
line[i].isspace() and \
|
||||
brackdepth == 1 and \
|
||||
parendepth == 0:
|
||||
arg = arg.strip()
|
||||
if arg.startswith('"') and arg.endswith('"'):
|
||||
arg = arg[1:-1].strip()
|
||||
args.append(arg)
|
||||
arg = ""
|
||||
elif not line[i].isspace() or parendepth > 0:
|
||||
arg += line[i]
|
||||
return (args, brackdepth, parendepth)
|
||||
|
||||
def formaltype(f):
|
||||
# Deduce the expected type of the formal
|
||||
if f.startswith("_"):
|
||||
f = f[1:]
|
||||
if f == "SIDE" or f.endswith("_SIDE") or re.match("SIDE[0-9]", f):
|
||||
ftype = "side"
|
||||
elif f in ("SIDE", "X", "Y", "RED", "GREEN", "BLUE", "TURN", "PROB", "LAYER", "TIME", "DURATION") or f.endswith("NUMBER") or f.endswith("AMOUNT") or f.endswith("COST") or f.endswith("RADIUS") or f.endswith("_X") or f.endswith("_Y") or f.endswith("_INCREMENT") or f.endswith("_FACTOR") or f.endswith("_TIME") or f.endswith("_SIZE"):
|
||||
ftype = "numeric"
|
||||
elif f.endswith("PERCENTAGE"):
|
||||
ftype = "percentage"
|
||||
elif f in ("POSITION",) or f.endswith("_POSITION") or f == "BASE":
|
||||
ftype = "position"
|
||||
elif f.endswith("_SPAN"):
|
||||
ftype = "span"
|
||||
elif f == "SIDES" or f.endswith("_SIDES"):
|
||||
ftype = "alliance"
|
||||
elif f in ("RANGE",):
|
||||
ftype = "range"
|
||||
elif f in ("ALIGN",):
|
||||
ftype = "alignment"
|
||||
elif f in ("TYPES"):
|
||||
ftype = "types"
|
||||
elif f.startswith("ADJACENT") or f.startswith("TERRAINLIST") or f == "RESTRICTING":
|
||||
ftype = "terrain_pattern"
|
||||
elif f.startswith("TERRAIN") or f.endswith("TERRAIN"):
|
||||
ftype = "terrain_code"
|
||||
elif f in ("NAME", "NAMESPACE", "VAR", "IMAGESTEM", "ID", "FLAG", "BUILDER") or f.endswith("_NAME") or f.endswith("_ID") or f.endswith("_VAR") or f.endswith("_OVERLAY"):
|
||||
ftype = "name"
|
||||
elif f in ("ID_STRING", "NAME_STRING", "DESCRIPTION", "IPF"):
|
||||
ftype = "optional_string"
|
||||
elif f in ("STRING", "TYPE", "TEXT") or f.endswith("_STRING") or f.endswith("_TYPE") or f.endswith("_TEXT"):
|
||||
ftype = "string"
|
||||
elif f.endswith("IMAGE") or f == "PROFILE":
|
||||
ftype = "image"
|
||||
elif f.endswith("MUSIC",) or f.endswith("SOUND"):
|
||||
ftype = "sound"
|
||||
elif f.endswith("FILTER",):
|
||||
ftype = "filter"
|
||||
elif f == "WML" or f.endswith("_WML"):
|
||||
ftype = "wml"
|
||||
elif f in ("AFFIX", "POSTFIX", "ROTATION") or f.endswith("AFFIX"):
|
||||
ftype = "affix"
|
||||
# The regexp case avoids complaints about some wacky terrain macros.
|
||||
elif f.endswith("VALUE") or re.match("[ARS][0-9]", f):
|
||||
ftype = "any"
|
||||
else:
|
||||
ftype = None
|
||||
return ftype
|
||||
|
||||
def actualtype(a):
|
||||
if a is None:
|
||||
return None
|
||||
# Deduce the type of the actual
|
||||
if a.isdigit() or a.startswith("-") and a[1:].isdigit():
|
||||
atype = "numeric"
|
||||
elif re.match(r"0\.[0-9]+\Z", a):
|
||||
atype = "percentage"
|
||||
elif re.match(r"-?[0-9]+,-?[0-9]+\Z", a):
|
||||
atype = "position"
|
||||
elif re.match(r"([0-9]+\-[0-9]+,?|[0-9]+,?)+\Z", a):
|
||||
atype = "span"
|
||||
elif a in ("melee", "ranged"):
|
||||
atype = "range"
|
||||
elif a in ("lawful", "neutral", "chaotic", "liminal"):
|
||||
atype = "alignment"
|
||||
elif a.startswith("{") or a.endswith("}") or a.startswith("$"):
|
||||
atype = None # Can't tell -- it's a macro expansion
|
||||
elif re.match(image_reference, a) or a == "unit_image":
|
||||
atype = "image"
|
||||
elif re.match(r"(\*|[A-Z][a-z]+)\^([A-Z][a-z\\|/]+\Z)?", a):
|
||||
atype = "terrain_code"
|
||||
elif a.endswith(".wav") or a.endswith(".ogg"):
|
||||
atype = "sound"
|
||||
elif a.startswith('"') and a.endswith('"') or (a.startswith("_") and a[1] not in string.ascii_lowercase):
|
||||
atype = "stringliteral"
|
||||
elif "=" in a:
|
||||
atype = "filter"
|
||||
elif re.match(r"[A-Z][a-z][a-z]?\Z", a):
|
||||
atype = "shortname"
|
||||
elif a == "":
|
||||
atype = "empty"
|
||||
elif not ' ' in a:
|
||||
atype = "name"
|
||||
else:
|
||||
atype = "string"
|
||||
return atype
|
||||
|
||||
def argmatch(formals, actuals):
|
||||
if len(formals) != len(actuals):
|
||||
return False
|
||||
for (f, a) in zip(formals, actuals):
|
||||
# Here's the compatibility logic. First, we catch the situations
|
||||
# in which a more restricted actual type matches a more general
|
||||
# formal one. Then we have a fallback rule checking for type
|
||||
# equality or wildcarding.
|
||||
ftype = formaltype(f)
|
||||
atype = actualtype(a)
|
||||
if ftype == "any":
|
||||
pass
|
||||
elif (atype == "numeric" or a == "global") and ftype == "side":
|
||||
pass
|
||||
elif atype in ("filter", "empty") and ftype == "wml":
|
||||
pass
|
||||
elif atype in ("numeric", "position") and ftype == "span":
|
||||
pass
|
||||
elif atype in ("shortname", "name", "empty", "stringliteral") and ftype == "affix":
|
||||
pass
|
||||
elif atype in ("shortname", "name", "stringliteral") and ftype == "string":
|
||||
pass
|
||||
elif atype in ("shortname", "name", "string", "stringliteral", "empty") and ftype == "optional_string":
|
||||
pass
|
||||
elif atype in ("shortname",) and ftype == "terrain_code":
|
||||
pass
|
||||
elif atype in ("numeric", "position", "span", "empty") and ftype == "alliance":
|
||||
pass
|
||||
elif atype in ("terrain_code", "shortname", "name") and ftype == "terrain_pattern":
|
||||
pass
|
||||
elif atype in ("string", "shortname", "name") and ftype == "types":
|
||||
pass
|
||||
elif atype in ("numeric", "percentage") and ftype == "percentage":
|
||||
pass
|
||||
elif atype == "range" and ftype == "name":
|
||||
pass
|
||||
elif atype != ftype and ftype is not None and atype is not None:
|
||||
return False
|
||||
return True
|
||||
|
||||
# the total_ordering decorator from functools allows to define only two comparison
|
||||
# methods, and Python generates the remaining methods
|
||||
# it comes with a speed penalty, but the alternative is defining six methods by hand...
|
||||
@total_ordering
|
||||
class Reference:
|
||||
"Describes a location by file and line."
|
||||
def __init__(self, namespace, filename, lineno=None, docstring=None, args=None):
|
||||
self.namespace = namespace
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.docstring = docstring
|
||||
self.args = args
|
||||
self.references = collections.defaultdict(list)
|
||||
self.undef = None
|
||||
|
||||
def append(self, fn, n, a=None):
|
||||
self.references[fn].append((n, a))
|
||||
|
||||
def dump_references(self):
|
||||
"Dump all known references to this definition."
|
||||
for (file, refs) in self.references.items():
|
||||
print(" %s: %s" % (file, repr([x[0] for x in refs])[1:-1]))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.filename == other.filename and self.lineno == other.lineno
|
||||
|
||||
def __gt__(self, other):
|
||||
# Major sort by file, minor by line number. This presumes that the
|
||||
# files correspond to coherent topics and gives us control of the
|
||||
# sequence.
|
||||
if self.filename == other.filename:
|
||||
return self.lineno > other.lineno
|
||||
else:
|
||||
return self.filename > other.filename
|
||||
|
||||
def mismatches(self):
|
||||
copy = Reference(self.namespace, self.filename, self.lineno, self.docstring, self.args)
|
||||
copy.undef = self.undef
|
||||
for filename in self.references:
|
||||
mis = [(ln,a) for (ln,a) in self.references[filename] if a is not None and not argmatch(self.args, a)]
|
||||
if mis:
|
||||
copy.references[filename] = mis
|
||||
return copy
|
||||
def __str__(self):
|
||||
if self.lineno:
|
||||
return '"%s", line %d' % (self.filename, self.lineno)
|
||||
else:
|
||||
return self.filename
|
||||
__repr__ = __str__
|
||||
|
||||
class CrossRef:
|
||||
macro_reference = re.compile(r"\{([A-Z_][A-Za-z0-9_:]*)(?!\.)\b")
|
||||
file_reference = re.compile(r"[A-Za-z0-9{}.][A-Za-z0-9_/+{}.@-]*\.(" + "|".join(resource_extensions) + ")(?=(~.*)?)")
|
||||
tag_parse = re.compile("\s*([a-z_]+)\s*=(.*)")
|
||||
def mark_matching_resources(self, pattern, fn, n):
|
||||
"Mark all definitions matching a specified pattern with a reference."
|
||||
pattern = pattern.replace("+", r"\+")
|
||||
pattern = os.sep + pattern + "$"
|
||||
if os.sep == "\\":
|
||||
pattern = pattern.replace("\\", "\\\\")
|
||||
try:
|
||||
pattern = re.compile(pattern)
|
||||
except sre_constants.error:
|
||||
print("wmlscope: confused by %s" % pattern, file=sys.stderr)
|
||||
return None
|
||||
key = None
|
||||
for trial in self.fileref:
|
||||
if pattern.search(trial) and self.visible_from(trial, fn, n):
|
||||
key = trial
|
||||
self.fileref[key].append(fn, n)
|
||||
return key
|
||||
def visible_from(self, defn, fn, n):
|
||||
"Is specified definition visible from the specified file and line?"
|
||||
if isinstance(defn, str):
|
||||
defn = self.fileref[defn]
|
||||
if defn.undef is not None:
|
||||
# Local macros are only visible in the file where they were defined
|
||||
return defn.filename == fn and n >= defn.lineno and n <= defn.undef
|
||||
if self.exports(defn.namespace):
|
||||
# Macros and resources in subtrees with export=yes are global
|
||||
return True
|
||||
elif not self.filelist.neighbors(defn.filename, fn):
|
||||
# Otherwise, must be in the same subtree.
|
||||
return False
|
||||
else:
|
||||
# If the two files are in the same subtree, assume visibility.
|
||||
# This doesn't match the actual preprocessor semantics.
|
||||
# It means any macro without an undef is visible anywhere in the
|
||||
# same argument directory.
|
||||
#
|
||||
# We can't do better than this without a lot of hairy graph-
|
||||
# coloring logic to simulate include path interpretation.
|
||||
# If that logic ever gets built, it will go here.
|
||||
return True
|
||||
def scan_for_definitions(self, namespace, filename):
|
||||
ignoreflag = False
|
||||
conditionalsflag = False
|
||||
with codecs.open(filename, "r", "utf8") as dfp:
|
||||
state = "outside"
|
||||
latch_unit = in_base_unit = in_theme = False
|
||||
for (n, line) in enumerate(dfp):
|
||||
if self.warnlevel > 1:
|
||||
print(repr(line)[1:-1])
|
||||
if line.strip().startswith("#textdomain"):
|
||||
continue
|
||||
m = re.search("# *wmlscope: warnlevel ([0-9]*)", line)
|
||||
if m:
|
||||
self.warnlevel = int(m.group(1))
|
||||
print('"%s", line %d: warnlevel set to %d (definition-gathering pass)' \
|
||||
% (filename, n+1, self.warnlevel))
|
||||
continue
|
||||
m = re.search("# *wmlscope: set *([^=]*)=(.*)", line)
|
||||
if m:
|
||||
prop = m.group(1).strip()
|
||||
value = m.group(2).strip()
|
||||
if namespace not in self.properties:
|
||||
self.properties[namespace] = {}
|
||||
self.properties[namespace][prop] = value
|
||||
m = re.search("# *wmlscope: prune (.*)", line)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
if self.warnlevel >= 2:
|
||||
print('"%s", line %d: pruning definitions of %s' \
|
||||
% (filename, n+1, name ))
|
||||
if name not in self.xref:
|
||||
print("wmlscope: can't prune undefined macro %s" % name, file=sys.stderr)
|
||||
else:
|
||||
self.xref[name] = self.xref[name][:1]
|
||||
continue
|
||||
if "# wmlscope: start conditionals" in line:
|
||||
if self.warnlevel > 1:
|
||||
print('"%s", line %d: starting conditionals' \
|
||||
% (filename, n+1))
|
||||
conditionalsflag = True
|
||||
elif "# wmlscope: stop conditionals" in line:
|
||||
if self.warnlevel > 1:
|
||||
print('"%s", line %d: stopping conditionals' \
|
||||
% (filename, n+1))
|
||||
conditionalsflag = False
|
||||
if "# wmlscope: start ignoring" in line:
|
||||
if self.warnlevel > 1:
|
||||
print('"%s", line %d: starting ignoring (definition pass)' \
|
||||
% (filename, n+1))
|
||||
ignoreflag = True
|
||||
elif "# wmlscope: stop ignoring" in line:
|
||||
if self.warnlevel > 1:
|
||||
print('"%s", line %d: stopping ignoring (definition pass)' \
|
||||
% (filename, n+1))
|
||||
ignoreflag = False
|
||||
elif ignoreflag:
|
||||
continue
|
||||
if line.strip().startswith("#define"):
|
||||
tokens = line.split()
|
||||
if len(tokens) < 2:
|
||||
print('"%s", line %d: malformed #define' \
|
||||
% (filename, n+1), file=sys.stderr)
|
||||
else:
|
||||
name = tokens[1]
|
||||
here = Reference(namespace, filename, n+1, line, args=tokens[2:])
|
||||
here.hash = hashlib.md5()
|
||||
here.docstring = line.lstrip()[8:] # Strip off #define_
|
||||
state = "macro_header"
|
||||
continue
|
||||
elif state != 'outside' and line.strip().endswith("#enddef"):
|
||||
here.hash.update(line.encode("utf8"))
|
||||
here.hash = here.hash.digest()
|
||||
if name in self.xref:
|
||||
for defn in self.xref[name]:
|
||||
if not self.visible_from(defn, filename, n+1):
|
||||
continue
|
||||
elif conditionalsflag:
|
||||
continue
|
||||
elif defn.hash != here.hash:
|
||||
print("%s: overrides different %s definition at %s" \
|
||||
% (here, name, defn), file=sys.stderr)
|
||||
elif self.warnlevel > 0:
|
||||
print("%s: duplicates %s definition at %s" \
|
||||
% (here, name, defn), file=sys.stderr)
|
||||
if name not in self.xref:
|
||||
self.xref[name] = []
|
||||
self.xref[name].append(here)
|
||||
state = "outside"
|
||||
elif state == "macro_header" and line.strip() and line.strip()[0] != "#":
|
||||
state = "macro_body"
|
||||
if state == "macro_header":
|
||||
# Ignore macro header commends that are pragmas
|
||||
if "wmlscope" not in line and "wmllint:" not in line:
|
||||
here.docstring += line.lstrip()[1:]
|
||||
if state in ("macro_header", "macro_body"):
|
||||
here.hash.update(line.encode("utf8"))
|
||||
elif line.strip().startswith("#undef"):
|
||||
tokens = line.split()
|
||||
name = tokens[1]
|
||||
if name in self.xref and self.xref[name]:
|
||||
self.xref[name][-1].undef = n+1
|
||||
else:
|
||||
print("%s: unbalanced #undef on %s" \
|
||||
% (Reference(namespace, filename, n+1), name))
|
||||
if state == 'outside':
|
||||
if '[unit_type]' in line:
|
||||
latch_unit = True
|
||||
elif '[/unit_type]' in line:
|
||||
latch_unit = False
|
||||
elif '[base_unit]' in line:
|
||||
in_base_unit = True
|
||||
elif '[/base_unit]' in line:
|
||||
in_base_unit = False
|
||||
elif '[theme]' in line:
|
||||
in_theme = True
|
||||
elif '[/theme]' in line:
|
||||
in_theme = False
|
||||
elif latch_unit and not in_base_unit and not in_theme and "id" in line:
|
||||
m = CrossRef.tag_parse.search(line)
|
||||
if m and m.group(1) == "id":
|
||||
uid = m.group(2)
|
||||
if uid not in self.unit_ids:
|
||||
self.unit_ids[uid] = []
|
||||
self.unit_ids[uid].append(Reference(namespace, filename, n+1))
|
||||
latch_unit= False
|
||||
def __init__(self, dirpath=[], exclude="", warnlevel=0, progress=False):
|
||||
"Build cross-reference object from the specified filelist."
|
||||
self.filelist = Forest(dirpath, exclude)
|
||||
self.dirpath = [x for x in dirpath if not re.search(exclude, x)]
|
||||
self.warnlevel = warnlevel
|
||||
self.xref = {}
|
||||
self.fileref = {}
|
||||
self.noxref = False
|
||||
self.properties = {}
|
||||
self.unit_ids = {}
|
||||
all_in = []
|
||||
if self.warnlevel >=2 or progress:
|
||||
print("*** Beginning definition-gathering pass...")
|
||||
for (namespace, filename) in self.filelist.generator():
|
||||
all_in.append((namespace, filename))
|
||||
if self.warnlevel > 1:
|
||||
print(filename + ":")
|
||||
if progress:
|
||||
print(filename)
|
||||
if isresource(filename):
|
||||
self.fileref[filename] = Reference(namespace, filename)
|
||||
elif iswml(filename):
|
||||
# It's a WML file, scan for macro definitions
|
||||
self.scan_for_definitions(namespace, filename)
|
||||
elif filename.endswith(".def"):
|
||||
# It's a list of names to be considered defined
|
||||
self.noxref = True
|
||||
with codecs.open(filename, "r", "utf8") as dfp:
|
||||
for line in dfp:
|
||||
self.xref[line.strip()] = True
|
||||
# Next, decorate definitions with all references from the filelist.
|
||||
self.unresolved = []
|
||||
self.missing = []
|
||||
formals = []
|
||||
state = "outside"
|
||||
if self.warnlevel >=2 or progress:
|
||||
print("*** Beginning reference-gathering pass...")
|
||||
for (ns, fn) in all_in:
|
||||
if progress:
|
||||
print(filename)
|
||||
if iswml(fn):
|
||||
with codecs.open(fn, "r", "utf8") as rfp:
|
||||
attack_name = None
|
||||
beneath = 0
|
||||
ignoreflag = False
|
||||
for (n, line) in enumerate(rfp):
|
||||
if line.strip().startswith("#define"):
|
||||
formals = line.strip().split()[2:]
|
||||
elif line.startswith("#enddef"):
|
||||
formals = []
|
||||
comment = ""
|
||||
if '#' in line:
|
||||
if "# wmlscope: start ignoring" in line:
|
||||
if self.warnlevel > 1:
|
||||
print('"%s", line %d: starting ignoring (reference pass)' \
|
||||
% (fn, n+1))
|
||||
ignoreflag = True
|
||||
elif "# wmlscope: stop ignoring" in line:
|
||||
if self.warnlevel > 1:
|
||||
print('"%s", line %d: stopping ignoring (reference pass)' \
|
||||
% (fn, n+1))
|
||||
ignoreflag = False
|
||||
m = re.search("# *wmlscope: self.warnlevel ([0-9]*)", line)
|
||||
if m:
|
||||
self.warnlevel = int(m.group(1))
|
||||
print('"%s", line %d: self.warnlevel set to %d (reference-gathering pass)' \
|
||||
% (fn, n+1, self.warnlevel))
|
||||
continue
|
||||
fields = line.split('#')
|
||||
line = fields[0]
|
||||
if len(fields) > 1:
|
||||
comment = fields[1]
|
||||
if ignoreflag or not line:
|
||||
continue
|
||||
# Find references to macros
|
||||
for match in re.finditer(CrossRef.macro_reference, line):
|
||||
name = match.group(1)
|
||||
candidates = []
|
||||
if self.warnlevel >=2:
|
||||
print('"%s", line %d: seeking definition of %s' \
|
||||
% (fn, n+1, name))
|
||||
if name in formals:
|
||||
continue
|
||||
elif name in self.xref:
|
||||
# Count the number of actual arguments.
|
||||
# Set args to None if the call doesn't
|
||||
# close on this line
|
||||
(args, brackdepth, parendepth) = parse_macroref(match.start(0), line)
|
||||
if brackdepth > 0 or parendepth > 0:
|
||||
args = None
|
||||
else:
|
||||
args.pop(0)
|
||||
#if args:
|
||||
# print('"%s", line %d: args of %s is %s' \
|
||||
# % (fn, n+1, name, args))
|
||||
# Figure out which macros might resolve this
|
||||
for defn in self.xref[name]:
|
||||
if self.visible_from(defn, fn, n+1):
|
||||
defn.append(fn, n+1, args)
|
||||
candidates.append(str(defn))
|
||||
if len(candidates) > 1:
|
||||
print("%s: more than one definition of %s is visible here (%s)." % (Reference(ns, fn, n), name, "; ".join(candidates)))
|
||||
if len(candidates) == 0:
|
||||
self.unresolved.append((name,Reference(ns,fn,n+1)))
|
||||
# Don't be fooled by HTML image references in help strings.
|
||||
if "<img>" in line:
|
||||
continue
|
||||
# Find references to resource files
|
||||
for match in re.finditer(CrossRef.file_reference, line):
|
||||
name = match.group(0)
|
||||
# Catches maps that look like macro names.
|
||||
if (name.endswith(".map") or name.endswith(".mask")) and name[0] == '{':
|
||||
name = name[1:]
|
||||
if os.sep == "\\":
|
||||
name = name.replace("/", "\\")
|
||||
key = None
|
||||
# If name is already in our resource list, it's easy.
|
||||
if name in self.fileref and self.visible_from(name, fn, n):
|
||||
self.fileref[name].append(fn, n+1)
|
||||
continue
|
||||
# If the name contains substitutable parts, count
|
||||
# it as a reference to everything the substitutions
|
||||
# could potentially match.
|
||||
elif '{' in name or '@' in name:
|
||||
pattern = re.sub(r"(\{[^}]*\}|@R0|@V)", '.*', name)
|
||||
key = self.mark_matching_resources(pattern, fn,n+1)
|
||||
if key:
|
||||
self.fileref[key].append(fn, n+1)
|
||||
else:
|
||||
candidates = []
|
||||
for trial in self.fileref:
|
||||
if trial.endswith(os.sep + name) and self.visible_from(trial, fn, n):
|
||||
key = trial
|
||||
self.fileref[trial].append(fn, n+1)
|
||||
candidates.append(trial)
|
||||
if len(candidates) > 1:
|
||||
print("%s: more than one resource matching %s is visible here (%s)." % (Reference(ns,fn, n), name, ", ".join(candidates)))
|
||||
if not key:
|
||||
self.missing.append((name, Reference(ns,fn,n+1)))
|
||||
# Notice implicit references through attacks
|
||||
if state == "outside":
|
||||
if "[attack]" in line:
|
||||
beneath = 0
|
||||
attack_name = default_icon = None
|
||||
have_icon = False
|
||||
elif "name=" in line and not "no-icon" in comment:
|
||||
attack_name = line[line.find("name=")+5:].strip()
|
||||
default_icon = os.path.join("attacks", attack_name + ".png")
|
||||
elif "icon=" in line and beneath == 0:
|
||||
have_icon = True
|
||||
elif "[/attack]" in line:
|
||||
if attack_name and not have_icon:
|
||||
candidates = []
|
||||
key = None
|
||||
for trial in self.fileref:
|
||||
if trial.endswith(os.sep + default_icon) and self.visible_from(trial, fn, n):
|
||||
key = trial
|
||||
self.fileref[trial].append(fn, n+1)
|
||||
candidates.append(trial)
|
||||
if len(candidates) > 1:
|
||||
print("%s: more than one definition of %s is visible here (%s)." % (Reference(ns,fn, n), name, ", ".join(candidates)))
|
||||
if not key:
|
||||
self.missing.append((default_icon, Reference(ns,fn,n+1)))
|
||||
elif line.strip().startswith("[/"):
|
||||
beneath -= 1
|
||||
elif line.strip().startswith("["):
|
||||
beneath += 1
|
||||
# Check whether each namespace has a defined export property
|
||||
for namespace in self.dirpath:
|
||||
if namespace not in self.properties or "export" not in self.properties[namespace]:
|
||||
print("warning: %s has no export property" % namespace)
|
||||
def exports(self, namespace):
|
||||
return namespace in self.properties and self.properties[namespace].get("export") == "yes"
|
||||
def subtract(self, filelist):
|
||||
|
||||
"Transplant file references in files from filelist to a new CrossRef."
|
||||
smallref = CrossRef()
|
||||
for filename in self.fileref:
|
||||
for (referrer, referlines) in self.fileref[filename].references.items():
|
||||
if referrer in filelist:
|
||||
if filename not in smallref.fileref:
|
||||
smallref.fileref[filename] = Reference(None, filename)
|
||||
smallref.fileref[filename].references[referrer] = referlines
|
||||
del self.fileref[filename].references[referrer]
|
||||
return smallref
|
||||
def refcount(self, name):
|
||||
"Return a reference count for the specified resource."
|
||||
try:
|
||||
return len(self.fileref[name].references)
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
#
|
||||
# String translations from po files. The advantage of this code is that it
|
||||
# does not require the gettext binary message catalogs to have been compiled.
|
||||
# The disadvantage is that it eats lots of core!
|
||||
#
|
||||
|
||||
|
||||
class TranslationError(Exception):
|
||||
def __init__(self, textdomain, isocode):
|
||||
self.isocode = isocode
|
||||
self.textdomain = textdomain
|
||||
def __str__(self):
|
||||
return "No translations found for %s/%s.\n" % (
|
||||
self.textdomain, self.isocode)
|
||||
|
||||
class Translation(dict):
|
||||
"Parses a po file to create a translation dictionary."
|
||||
def __init__(self, textdomain, isocode, topdir=""):
|
||||
self.textdomain = textdomain
|
||||
self.isocode = isocode
|
||||
self.gettext = {}
|
||||
if self.isocode != "C":
|
||||
isocode2 = isocode[:isocode.rfind("_")]
|
||||
for code in [isocode, isocode2]:
|
||||
fn = "po/%s/%s.po" % (textdomain, code)
|
||||
if topdir: fn = os.path.join(topdir, fn)
|
||||
try:
|
||||
f = file(fn)
|
||||
break
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
raise TranslationError(textdomain, self.isocode)
|
||||
|
||||
expect = False
|
||||
fuzzy = "#, fuzzy\n"
|
||||
gettext = f.read().decode("utf8")
|
||||
matches = re.compile("""(msgid|msgstr)((\s*".*?")+)""").finditer(gettext)
|
||||
msgid = ""
|
||||
for match in matches:
|
||||
text = "".join(re.compile('"(.*?)"').findall(match.group(2)))
|
||||
if match.group(1) == "msgid":
|
||||
msgid = text.replace("\\n", "\n")
|
||||
expect = gettext[match.start(1) - len(fuzzy):match.start(1)] != fuzzy
|
||||
elif expect:
|
||||
self.gettext[msgid] = text.replace("\\n", "\n")
|
||||
def get(self, key, dflt):
|
||||
if self.isocode == "C":
|
||||
if key:
|
||||
return key[key.find("^") + 1:]
|
||||
return "?"
|
||||
else:
|
||||
t = self.gettext.get(key, dflt)
|
||||
if not t:
|
||||
if key:
|
||||
return key[key.find("^") + 1:]
|
||||
return "?"
|
||||
return t
|
||||
def __getitem__(self, key):
|
||||
if self.isocode == "C":
|
||||
return key
|
||||
else:
|
||||
return self.gettext[key]
|
||||
def __contains__(self, key):
|
||||
if self.isocode == "C":
|
||||
return True
|
||||
else:
|
||||
return key in self.gettext
|
||||
|
||||
class Translations:
|
||||
"Wraps around Translation to support multiple languages and domains."
|
||||
def __init__(self, topdir = ""):
|
||||
self.translations = {}
|
||||
self.topdir = topdir
|
||||
def get(self, textdomain, isocode, key, default):
|
||||
t = (textdomain, isocode)
|
||||
if not t in self.translations:
|
||||
try:
|
||||
self.translations[t] = Translation(textdomain, isocode, self.topdir)
|
||||
except TranslationError as e:
|
||||
print(str(e), file=sys.stderr)
|
||||
self.translations[t] = Translation(textdomain, "C", self.topdir)
|
||||
result = self.translations[t].get(key, default)
|
||||
return result
|
||||
|
||||
## Namespace management
|
||||
#
|
||||
# This is the only part of the code that actually knows about the
|
||||
# shape of the data tree.
|
||||
|
||||
def scopelist():
|
||||
"Return a list of (separate) package scopes, core first."
|
||||
return ["data/core"] + glob.glob("data/campaigns/*")
|
||||
|
||||
def is_namespace(name):
|
||||
"Is the name either a valid campaign name or core?"
|
||||
return name in map(os.path.basename, scopelist())
|
||||
|
||||
def namespace_directory(name):
|
||||
"Go from namespace to directory."
|
||||
if name == "core":
|
||||
return "data/core/"
|
||||
else:
|
||||
return "data/campaigns/" + name + "/"
|
||||
|
||||
def directory_namespace(path):
|
||||
"Go from directory to namespace."
|
||||
if path.startswith("data/core/"):
|
||||
return "core"
|
||||
elif path.startswith("data/campaigns/"):
|
||||
return path.split("/")[2]
|
||||
else:
|
||||
return None
|
||||
|
||||
def namespace_member(path, namespace):
|
||||
"Is a path in a specified namespace?"
|
||||
ns = directory_namespace(path)
|
||||
return ns is not None and ns == namespace
|
||||
|
||||
def resolve_unit_cfg(namespace, utype, resource=None):
|
||||
"Get the location of a specified unit in a specified scope."
|
||||
if resource:
|
||||
resource = os.path.join(utype, resource)
|
||||
else:
|
||||
resource = utype
|
||||
loc = namespace_directory(namespace) + "units/" + resource
|
||||
if not loc.endswith(".cfg"):
|
||||
loc += ".cfg"
|
||||
return loc
|
||||
|
||||
def resolve_unit_image(namespace, subdir, resource):
|
||||
"Construct a plausible location for given resource in specified namespace."
|
||||
return os.path.join(namespace_directory(namespace), "images/units", subdir, resource)
|
||||
|
||||
# And this is for code that does syntax transformation
|
||||
baseindent = " "
|
||||
|
||||
## Version-control hooks begin here.
|
||||
#
|
||||
# Not tested since the git transition
|
||||
|
||||
vcdir = ".git"
|
||||
|
||||
def vcmove(src, dst):
|
||||
"Move a file under version control. Only applied to unmodified files."
|
||||
(path, base) = os.path.split(src)
|
||||
if os.path.exists(os.path.join(path, ".git")):
|
||||
return "git mv %s %s" % (src, dst)
|
||||
else:
|
||||
return "echo 'cannot move %s to %s, .git is missing'" % (src, dst)
|
||||
|
||||
def vcunmove(src, dst):
|
||||
"Revert the result of a previous move (before commit)."
|
||||
(path, base) = os.path.split(src)
|
||||
if os.path.exists(os.path.join(path, ".git")):
|
||||
return "git checkout %s" % dst # Revert the add at the destination
|
||||
return "git rm " + dst # Remove the moved copy
|
||||
return "git checkout %s" % src # Revert the deletion
|
||||
else:
|
||||
return "echo 'cannot unmove %s from %s, .git is missing'" % (src, dst)
|
||||
|
||||
def vcdelete(src):
|
||||
"Delete a file under version control."
|
||||
(path, base) = os.path.split(src)
|
||||
if os.path.exists(os.path.join(path, ".git")):
|
||||
return "git rm %s" % src
|
||||
else:
|
||||
return "echo 'cannot undelete %s, .git is missing'" % src
|
||||
|
||||
def vcundelete(src):
|
||||
"Revert the result of a previous delete (before commit)."
|
||||
(path, base) = os.path.split(src)
|
||||
if os.path.exists(os.path.join(path, ".git")):
|
||||
return "git checkout %s" % src # Revert the deletion
|
||||
else:
|
||||
return "echo 'cannot undelete %s, .git is missing'" % src
|
||||
|
||||
#
|
||||
## Version-control hooks end here
|
||||
|
||||
# wmltools.py ends here
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# encoding: utf8
|
||||
"""
|
||||
add-on_manager.py -- a command-line client for the Wesnoth add-on server
|
||||
|
@ -11,8 +11,7 @@ add-ons.
|
|||
|
||||
import sys, os.path, re, time, glob, shutil
|
||||
from subprocess import Popen
|
||||
import wesnoth.wmldata as wmldata
|
||||
import wesnoth.wmlparser as wmlparser
|
||||
import wesnoth.wmlparser3 as wmlparser
|
||||
from wesnoth.campaignserver_client import CampaignClient
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -32,7 +31,7 @@ if __name__ == "__main__":
|
|||
help="Output a HTML overview into the given directory.",)
|
||||
argumentparser.add_argument("-p", "--port",
|
||||
help="specify server port or BfW version (%s)" % " or ".join(
|
||||
map(lambda x: x[1], CampaignClient.portmap)),
|
||||
[x[1] for x in CampaignClient.portmap]),
|
||||
default=CampaignClient.portmap[0][0])
|
||||
argumentparser.add_argument("-l", "--list", help="list available add-ons",
|
||||
action="store_true",)
|
||||
|
@ -112,7 +111,7 @@ if __name__ == "__main__":
|
|||
while not mythread.event.isSet():
|
||||
mythread.event.wait(1)
|
||||
if pcounter != cs.counter:
|
||||
print "%s: %d/%d" % (name, cs.counter, cs.length)
|
||||
print("%s: %d/%d" % (name, cs.counter, cs.length))
|
||||
pcounter = cs.counter
|
||||
|
||||
if args.raw_download:
|
||||
|
@ -127,13 +126,13 @@ if __name__ == "__main__":
|
|||
try: os.remove(oldcfg_path)
|
||||
except OSError: pass
|
||||
|
||||
print "Unpacking %s..." % name
|
||||
print("Unpacking %s..." % name)
|
||||
cs.unpackdir(decoded, cdir, verbose=args.verbose)
|
||||
|
||||
|
||||
info = os.path.join(dirname, "_info.cfg")
|
||||
try:
|
||||
f = file(info, "w")
|
||||
f = open(info, "w")
|
||||
infowml = """#
|
||||
# File automatically generated by Wesnoth to keep track
|
||||
# of version information on installed add-ons. DO NOT EDIT!
|
||||
|
@ -151,8 +150,7 @@ if __name__ == "__main__":
|
|||
f.close()
|
||||
except OSError:
|
||||
pass
|
||||
for message in decoded.find_all("message", "error"):
|
||||
print message.get_text_val("message")
|
||||
print_messages(decoded)
|
||||
|
||||
if args.tar:
|
||||
try: os.mkdir(args.tar)
|
||||
|
@ -171,6 +169,15 @@ if __name__ == "__main__":
|
|||
"cjf %(tarname)s -C %(cdir)s %(name)s\n" % locals())
|
||||
Popen(["tar", "cjf", tarname, "-C", cdir, name])
|
||||
|
||||
def print_messages(data):
|
||||
for message in data.get_all(tag = "message") + data.get_all(tag = "error"):
|
||||
print(message.get_text_val("message"))
|
||||
|
||||
def parse_wml_file(name):
|
||||
p = wmlparser.Parser()
|
||||
p.parse_file(name)
|
||||
return p.root
|
||||
|
||||
def get_info(name):
|
||||
"""
|
||||
Get info for a locally installed add-on. It expects a direct path
|
||||
|
@ -179,12 +186,9 @@ if __name__ == "__main__":
|
|||
if not os.path.exists(name):
|
||||
return None, None
|
||||
|
||||
p = wmlparser.Parser(None)
|
||||
p.parse_file(name)
|
||||
info = wmldata.DataSub("WML")
|
||||
p.parse_top(info)
|
||||
uploads = info.get_or_create_sub("info").get_text_val("uploads", "")
|
||||
version = info.get_or_create_sub("info").get_text_val("version", "")
|
||||
info = parse_wml_file(name)
|
||||
uploads = info.get_all(tag = "info")[0].get_text_val("uploads", "")
|
||||
version = info.get_all(tag = "info")[0].get_text_val("version", "")
|
||||
return uploads, version
|
||||
|
||||
campaign_list = None
|
||||
|
@ -193,17 +197,16 @@ if __name__ == "__main__":
|
|||
cs = CampaignClient(address)
|
||||
campaign_list = data = cs.list_campaigns()
|
||||
if data:
|
||||
campaigns = data.get_or_create_sub("campaigns")
|
||||
campaigns = data.get_all(tag = "campaigns")[0]
|
||||
if args.wml:
|
||||
for campaign in campaigns.get_all("campaign"):
|
||||
campaign.debug(show_contents=True,
|
||||
use_color=args.color)
|
||||
for campaign in campaigns.get_all(tag = "campaign"):
|
||||
print(campaign.debug())
|
||||
else:
|
||||
column_sizes = [10, 5, 10, 7, 8, 8, 10, 5, 10, 13]
|
||||
columns = [["type", "name", "title", "author",
|
||||
"version", "uploads", "downloads",
|
||||
"size", "timestamp", "translate"]]
|
||||
for campaign in campaigns.get_all("campaign"):
|
||||
for campaign in campaigns.get_all(tag = "campaign"):
|
||||
column = [
|
||||
campaign.get_text_val("type", "?"),
|
||||
campaign.get_text_val("name", "?"),
|
||||
|
@ -223,8 +226,7 @@ if __name__ == "__main__":
|
|||
for i, f in enumerate(c):
|
||||
sys.stdout.write(f.ljust(column_sizes[i]))
|
||||
sys.stdout.write("\n")
|
||||
for message in data.find_all("message", "error"):
|
||||
print message.get_text_val("message")
|
||||
print_messages(data)
|
||||
else:
|
||||
sys.stderr.write("Could not connect.\n")
|
||||
|
||||
|
@ -233,8 +235,8 @@ if __name__ == "__main__":
|
|||
fetchlist = []
|
||||
campaign_list = data = cs.list_campaigns()
|
||||
if data:
|
||||
campaigns = data.get_or_create_sub("campaigns")
|
||||
for campaign in campaigns.get_all("campaign"):
|
||||
campaigns = data.get_all(tag = "campaigns")[0]
|
||||
for campaign in campaigns.get_all(tag = "campaign"):
|
||||
name = campaign.get_text_val("name", "?")
|
||||
title = campaign.get_text_val("title")
|
||||
type = campaign.get_text_val("type", "")
|
||||
|
@ -256,31 +258,29 @@ if __name__ == "__main__":
|
|||
if version != local_version or uploads > local_uploads:
|
||||
get(name, title, version, type, uploads, dependencies, args.campaigns_dir)
|
||||
else:
|
||||
print "Not downloading", name, \
|
||||
print("Not downloading", name, \
|
||||
"as the version already is", local_version, \
|
||||
"(The add-on got re-uploaded.)"
|
||||
"(The add-on got re-uploaded.)")
|
||||
else:
|
||||
if args.verbose:
|
||||
print "Not downloading", name, \
|
||||
"because it is already up-to-date."
|
||||
print("Not downloading", name, \
|
||||
"because it is already up-to-date.")
|
||||
|
||||
elif args.unpack:
|
||||
cs = CampaignClient(address)
|
||||
data = file(args.unpack).read()
|
||||
decoded = cs.decode(data)
|
||||
print "Unpacking %s..." % args.unpack
|
||||
print("Unpacking %s..." % args.unpack)
|
||||
cs.unpackdir(decoded, args.campaigns_dir, verbose=True)
|
||||
elif args.remove:
|
||||
cs = CampaignClient(address)
|
||||
data = cs.delete_campaign(args.remove, args.password)
|
||||
for message in data.find_all("message", "error"):
|
||||
print message.get_text_val("message")
|
||||
print_messages(data)
|
||||
|
||||
elif args.change_passphrase:
|
||||
cs = CampaignClient(address)
|
||||
data = cs.change_passphrase(*args.change_passphrase)
|
||||
for message in data.find_all("message", "error"):
|
||||
print message.get_text_val("message")
|
||||
print_messages(data)
|
||||
|
||||
elif args.upload:
|
||||
cs = CampaignClient(address)
|
||||
|
@ -306,7 +306,7 @@ if __name__ == "__main__":
|
|||
if args.pbl:
|
||||
pblfile = args.pbl
|
||||
|
||||
pbl = wmldata.read_file(pblfile, "PBL")
|
||||
pbl = parse_wml_file(pblfile)
|
||||
if os.path.exists(ignfile):
|
||||
ign = open(ignfile).readlines()
|
||||
# strip line endings and whitespace
|
||||
|
@ -341,11 +341,10 @@ if __name__ == "__main__":
|
|||
while not mythread.event.isSet():
|
||||
mythread.event.wait(1)
|
||||
if cs.counter != pcounter:
|
||||
print "%d/%d" % (cs.counter, cs.length)
|
||||
print("%d/%d" % (cs.counter, cs.length))
|
||||
pcounter = cs.counter
|
||||
|
||||
for message in mythread.data.find_all("message", "error"):
|
||||
print message.get_text_val("message")
|
||||
print_messages(mythread.data)
|
||||
|
||||
elif args.update or args.status:
|
||||
if args.status:
|
||||
|
@ -360,7 +359,7 @@ if __name__ == "__main__":
|
|||
sys.stderr.write("Could not connect to the add-on server.\n")
|
||||
sys.exit(-1)
|
||||
campaigns = {}
|
||||
for c in data.get_or_create_sub("campaigns").get_all("campaign"):
|
||||
for c in data.get_all(tag = "campaigns")[0].get_all(tag = "campaign"):
|
||||
name = c.get_text_val("name")
|
||||
campaigns[name] = c
|
||||
for d in dirs:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""\
|
||||
wmlindent - re-indent WML in a uniform way.
|
||||
|
@ -61,11 +61,8 @@ if there's an indent open at end of file or if a closer occurs with
|
|||
indent already zero; these two conditions strongly suggest unbalanced WML.
|
||||
"""
|
||||
|
||||
from __future__ import print_function, unicode_literals, division
|
||||
from future_builtins import filter, map, zip
|
||||
|
||||
import sys, os, getopt, filecmp, re, codecs
|
||||
from wesnoth import wmltools
|
||||
from wesnoth import wmltools3 as wmltools
|
||||
|
||||
closer_prefixes = ["{NEXT "]
|
||||
opener_prefixes = ["{FOREACH "]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# wmllint -- check WML for conformance to the most recent dialect
|
||||
|
@ -181,14 +181,9 @@
|
|||
# code.
|
||||
#
|
||||
|
||||
from __future__ import print_function, unicode_literals, division
|
||||
from future_builtins import filter, map, zip
|
||||
input = raw_input
|
||||
range = xrange
|
||||
|
||||
import sys, os, re, getopt, string, copy, difflib, time, gzip, codecs
|
||||
from wesnoth.wmltools import *
|
||||
from wesnoth.wmliterator import *
|
||||
from wesnoth.wmltools3 import *
|
||||
from wesnoth.wmliterator3 import *
|
||||
|
||||
# Changes meant to be done on maps and .cfg lines.
|
||||
mapchanges = (
|
||||
|
@ -2931,7 +2926,7 @@ In your case, your system interprets your arguments as:
|
|||
except:
|
||||
print("wmllint: internal error on %s" % fn, file=sys.stderr)
|
||||
(exc_type, exc_value, exc_traceback) = sys.exc_info()
|
||||
raise exc_type, exc_value, exc_traceback
|
||||
raise exc_type(exc_value).with_traceback(exc_traceback)
|
||||
if not clean and not diffs and not revert:
|
||||
# Consistency-check everything we got from the file scans
|
||||
if not inconsistency:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# wmllint -- check WML for conformance to the most recent dialect
|
||||
#
|
||||
|
@ -73,13 +73,9 @@
|
|||
# Note that this will also disable stack-based validation on the span
|
||||
# of lines they enclose.
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
from future_builtins import filter, map, zip
|
||||
range = xrange
|
||||
|
||||
import sys, os, re, getopt, string, copy, difflib, time, codecs
|
||||
from wesnoth.wmltools import *
|
||||
from wesnoth.wmliterator import *
|
||||
from wesnoth.wmltools3 import *
|
||||
from wesnoth.wmliterator3 import *
|
||||
|
||||
filemoves = {
|
||||
# Older includes all previous to 1.3.1.
|
||||
|
@ -1674,14 +1670,10 @@ def is_map(filename):
|
|||
has_map_content = False
|
||||
return has_map_content
|
||||
|
||||
class maptransform_error:
|
||||
class maptransform_error(Exception):
|
||||
"Error object to be thrown by maptransform."
|
||||
def __init__(self, infile, inline, type):
|
||||
self.infile = infile
|
||||
self.inline = inline
|
||||
self.type = type
|
||||
def __repr__(self):
|
||||
return '"%s", line %d: %s' % (self.infile, self.inline, self.type)
|
||||
def __init__(self, infile, inline, type_):
|
||||
self.message = '"%s", line %d: %s' % (infile, inline, type_)
|
||||
|
||||
tagstack = [] # For tracking tag nesting
|
||||
|
||||
|
@ -2279,12 +2271,12 @@ if __name__ == '__main__':
|
|||
os.rename(fn, backup)
|
||||
with codecs.open(fn, "w", "utf8") as ofp:
|
||||
ofp.write(changed)
|
||||
except maptransform_error, e:
|
||||
print("wmllint: " + repr(e), file=sys.stderr)
|
||||
except maptransform_error as e:
|
||||
print("wmllint: " + e.message, file=sys.stderr)
|
||||
except:
|
||||
print("wmllint: internal error on %s" % fn, file=sys.stderr)
|
||||
(exc_type, exc_value, exc_traceback) = sys.exc_info()
|
||||
raise exc_type, exc_value, exc_traceback
|
||||
raise exc_type(exc_value).with_traceback(exc_traceback)
|
||||
# Time for map and main file renames
|
||||
# FIXME: We should make some effort to rename mask files.
|
||||
if not revert and not diffs:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# wmlscope -- generate reports on WML macro and resource usage
|
||||
|
@ -93,11 +93,8 @@
|
|||
#
|
||||
# sets the warning level.
|
||||
|
||||
from __future__ import print_function, unicode_literals, division
|
||||
from future_builtins import filter, map, zip
|
||||
|
||||
import sys, os, time, re, getopt, hashlib, glob, codecs
|
||||
from wesnoth.wmltools import *
|
||||
from wesnoth.wmltools3 import *
|
||||
|
||||
def interpret(lines, css):
|
||||
"Interpret the ! convention for .cfg comments."
|
||||
|
@ -208,11 +205,10 @@ class CrossRefLister(CrossRef):
|
|||
# said bytes are placed in big-endian order (most significant bytes come first)
|
||||
# we need to use some bitwise operations to convert them as a single integer
|
||||
# also we don't need the remaining 5 bytes of the IHDR chunk
|
||||
# WARNING: for Python 3 ord() MUST be removed
|
||||
# this because Py2 reads the file as a string
|
||||
# while Py3 reads it as bytes, and each byte is already an int
|
||||
x = ord(w[0]) << 24 | ord(w[1]) << 16 | ord(w[2]) << 8 | ord(w[3])
|
||||
y = ord(h[0]) << 24 | ord(h[1]) << 16 | ord(h[2]) << 8 | ord(h[3])
|
||||
# Py3 reads the file as bytes, and each byte is already an int
|
||||
# this is why, unlike Python 2, ord() isn't needed
|
||||
x = w[0] << 24 | w[1] << 16 | w[2] << 8 | w[3]
|
||||
y = h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3]
|
||||
# these checks rely on add-ons that place files following mainline conventions
|
||||
# I'm aware that this may not always be the case
|
||||
# but the alternative will be implementing a more sophisticated check in wmllint
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#define FORMULA_STRING_UTILS_HPP_INCLUDED
|
||||
|
||||
#include "serialization/string_utils.hpp"
|
||||
#include <boost/assign.hpp>
|
||||
|
||||
class variable_set;
|
||||
|
||||
|
@ -52,6 +53,16 @@ std::string interpolate_variables_into_string(const std::string &str, const vari
|
|||
t_string interpolate_variables_into_tstring(const t_string &str, const variable_set& variables);
|
||||
|
||||
}
|
||||
/// An alias for boost::assign::map_list_of<std::string, std::string>
|
||||
inline boost::assign_detail::generic_list< std::pair
|
||||
<
|
||||
boost::assign_detail::assign_decay<std::string>::type,
|
||||
boost::assign_detail::assign_decay<std::string>::type
|
||||
> >
|
||||
string_map_of(const std::string& k, const std::string& v)
|
||||
{
|
||||
return boost::assign::map_list_of<std::string, std::string>(k, v);
|
||||
}
|
||||
|
||||
/** Handy wrappers around interpolate_variables_into_string and gettext. */
|
||||
std::string vgettext(const char* msgid, const utils::string_map& symbols);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include "formula_string_utils.hpp"
|
||||
|
||||
static lg::log_domain log_replay("replay");
|
||||
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
|
||||
|
@ -310,14 +311,14 @@ void user_choice_manager::update_local_choice()
|
|||
//equals to any side in sides that is local, 0 if no such side exists.
|
||||
local_choice_ = 0;
|
||||
//if for any side from which we need an answer
|
||||
wait_message_ = "";
|
||||
std::string sides_str;
|
||||
BOOST_FOREACH(int side, required_)
|
||||
{
|
||||
//and we havent already received our answer from that side
|
||||
if(res_.find(side) == res_.end())
|
||||
{
|
||||
wait_message_ += " ";
|
||||
wait_message_ += lexical_cast<std::string>(side);
|
||||
sides_str += " ";
|
||||
sides_str += lexical_cast<std::string>(side);
|
||||
//and it is local
|
||||
if((*resources::teams)[side-1].is_local() && !(*resources::teams)[side-1].is_idle())
|
||||
{
|
||||
|
@ -327,7 +328,7 @@ void user_choice_manager::update_local_choice()
|
|||
}
|
||||
}
|
||||
}
|
||||
wait_message_ = "waiting for " + uch_.description() + " from side(s)" + wait_message_;
|
||||
wait_message_ = vgettext("waiting for $desc from side(s)$sides", string_map_of("desc", uch_.description())("sides", sides_str));
|
||||
if(local_choice_prev != local_choice_) {
|
||||
changed_event_.notify_observers();
|
||||
}
|
||||
|
|
|
@ -147,3 +147,12 @@
|
|||
0 filter_this_unit_wml
|
||||
0 filter_this_unit_tl
|
||||
0 filter_this_unit_fai
|
||||
# Interrupt tag tests
|
||||
0 check_interrupts_break
|
||||
0 check_interrupts_return
|
||||
0 check_interrupts_continue
|
||||
0 check_interrupts_break_global
|
||||
0 check_interrupts_return_nested
|
||||
0 check_interrupts_continue_global
|
||||
0 check_interrupts_elseif
|
||||
0 check_interrupts_case
|
||||
|
|
Loading…
Add table
Reference in a new issue