Merge branch 'master' into sdl2

This commit is contained in:
Andreas Löf 2015-09-25 01:39:45 +12:00
commit 1d7e52c387
42 changed files with 2105 additions and 325 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -56,3 +56,11 @@
{CONTENT}
[/test]
#enddef
#define FAIL
{RETURN ([false][/false])}
#enddef
#define SUCCEED
{RETURN ([true][/true])}
#enddef

View file

@ -56,7 +56,7 @@
[/event]
[event]
name = side 2 turn 60
{RETURN ([true][/true])}
{SUCCEED}
[/event]
[event]
name = side 2 turn refresh

View file

@ -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

View file

@ -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]

View file

@ -42,7 +42,7 @@
[/event]
[event]
name = side 1 turn 7
{RETURN ([true][/true])}
{SUCCEED}
[/event]
[event]
name = side turn

View file

@ -157,6 +157,6 @@
{TEST_FEEDING bob 1}
{TEST_FEEDING bob 1}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -162,6 +162,6 @@
{assert_test_false (side=4) (side=2)}
{assert_test_false () (side=5)}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -116,6 +116,6 @@
[/have_unit]
[/not]
)}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View 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]
)}

View file

@ -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]
)}

View file

@ -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]
)}

View file

@ -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.

View file

@ -77,7 +77,7 @@
[/event]
[event]
name=side 2 turn 1
{RETURN ([false][/false])}
{FAIL}
[/event]
#enddef

View file

@ -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]
)}

View file

@ -8,6 +8,6 @@
[/event]
[event]
name = side 2 turn 1
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -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]

View file

@ -72,6 +72,6 @@
[/and]
[/not])}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -21,7 +21,7 @@
{ASSERT {VARIABLE_CONDITIONAL mx equals 3}}
{ASSERT {VARIABLE_CONDITIONAL my equals 3}}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -54,7 +54,7 @@
y={PY}
[/move]
[/do_command]
{RETURN ([true][/true])}
{SUCCEED}
[/event]
[/test]
#enddef

View file

@ -111,6 +111,6 @@
[/have_unit]
)}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -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]
)}

View file

@ -138,6 +138,6 @@
[/do_command]
{ASSERT_YES_9_5}
{RETURN ([true][/true])}
{SUCCEED}
[/event]
)}

View file

@ -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]
)}

View file

@ -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,

View file

@ -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", "")

View file

@ -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))

View file

@ -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

View 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

View 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

View file

@ -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:

View file

@ -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 "]

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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);

View file

@ -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();
}

View file

@ -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