diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java index 51df1a505..47f7dd30b 100644 --- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java @@ -1,6 +1,7 @@ package com.laytonsmith.core.functions; import com.laytonsmith.PureUtilities.Version; +import com.laytonsmith.abstraction.MCCommandSender; import com.laytonsmith.annotations.api; import com.laytonsmith.annotations.breakable; import com.laytonsmith.annotations.core; @@ -3217,6 +3218,79 @@ public CHVersion since() { } } + @api + @seealso({com.laytonsmith.tools.docgen.templates.Closures.class}) + public static class executeas extends AbstractFunction { + + @Override + public String getName() { + return "executeas"; + } + + @Override + public Integer[] numArgs() { + return new Integer[]{Integer.MAX_VALUE}; + } + + @Override + public String docs() { + return "mixed {player, label, [values...,] closure} Executes the given closure in the context of a given" + + " player. A closure that runs player(), for instance, would return the specified player's name." + + " The label argument sets the permission label that this closure will use. If null is given," + + " the current label will be used, like with execute()."; + } + + @Override + public Class[] thrown() { + return new Class[]{CRECastException.class}; + } + + @Override + public Boolean runAsync() { + return null; + } + + @Override + public boolean isRestricted() { + return true; + } + + @Override + public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException { + if(!(args[args.length - 1] instanceof CClosure)) { + throw new CRECastException("Only a closure (created from the closure function) can be sent to executeas()", t); + } + Construct[] vals = new Construct[args.length - 3]; + System.arraycopy(args, 2, vals, 0, args.length - 3); + CClosure closure = (CClosure) args[args.length - 1]; + CommandHelperEnvironment cEnv = closure.getEnv().getEnv(CommandHelperEnvironment.class); + GlobalEnv gEnv = closure.getEnv().getEnv(GlobalEnv.class); + + MCCommandSender originalSender = cEnv.GetCommandSender(); + cEnv.SetCommandSender(Static.GetCommandSender(args[0].val(), t)); + + String originalLabel = gEnv.GetLabel(); + if(!(args[1] instanceof CNull)) { + gEnv.SetLabel(args[1].val()); + } + + try { + closure.execute(vals); + } catch (FunctionReturnException e) { + return e.getReturn(); + } finally { + cEnv.SetCommandSender(originalSender); + gEnv.SetLabel(originalLabel); + } + return CVoid.VOID; + } + + @Override + public CHVersion since() { + return CHVersion.V3_3_2; + } + } + @api public static class _boolean extends AbstractFunction implements Optimizable { diff --git a/src/test/java/com/laytonsmith/core/functions/DataHandlingTest.java b/src/test/java/com/laytonsmith/core/functions/DataHandlingTest.java index d4add8616..84bea0ae9 100644 --- a/src/test/java/com/laytonsmith/core/functions/DataHandlingTest.java +++ b/src/test/java/com/laytonsmith/core/functions/DataHandlingTest.java @@ -49,6 +49,7 @@ public static void tearDownClass() throws Exception { public void setUp() { fakePlayer = StaticTest.GetOnlinePlayer(); fakeServer = StaticTest.GetFakeServer(); + when(fakeServer.getPlayer(fakePlayer.getName())).thenReturn(fakePlayer); env.getEnv(CommandHelperEnvironment.class).SetPlayer(fakePlayer); } @@ -475,6 +476,15 @@ public void testClosure9() throws Exception { verify(fakePlayer).sendMessage("{Hello World}"); } + @Test(timeout = 10000) + public void testClosure10() throws Exception { + SRun("@a = closure(msg('yes'));" + + "@b = closure(msg('no'));" + + "executeas('" + fakePlayer.getName() + "', null, @a);" + + "execute(@b);", fakeServer.getConsole()); + verify(fakePlayer).sendMessage("yes"); + } + @Test(timeout = 10000) public void testWhile() throws Exception { SRun("assign(@i, 2) while(@i > 0, @i-- msg('hi'))", fakePlayer);