$_ variable used in function from a module is empty (PowerShell) - variables

One question for you is here ;)
I have this function:
function Set-DbFile {
param(
[Parameter(ValueFromPipeline=$true)]
[System.IO.FileInfo[]]
$InputObject,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[scriptblock]
$Properties
)
process {
$InputObject | % {
Write-Host `nInside. Storing $_.Name
$props = & $Properties
Write-Host ' properties for the file are: ' -nonew
write-Host ($props.GetEnumerator()| %{"{0}-{1}" -f $_.key,$_.Value})
}
}
}
Look at the $Properties. It should be evaluated for each file and then the file and the properties should be processed further.
Example how to use it might be:
Get-ChildItem c:\windows |
? { !$_.PsIsContainer } |
Set-DbFile -prop {
Write-Host Creating properties for $_.FullName
#{Name=$_.Name } # any other properties based on the file
}
When I copy & paste function Set-dbFile to command line and run the example snippet, everything is fine.
However, when I store the function in a module, import it and run the example, the $_ variable is empty. Does anybody know why? And how to solve it? (other solutions are welcome as well)
Results for function defined in a script/typed in commandline:
Inside. Storing adsvw.ini
Creating properties for C:\windows\adsvw.ini
properties for the file are: Name-adsvw.ini
Inside. Storing ARJ.PIF
Creating properties for C:\windows\ARJ.PIF
properties for the file are: Name-ARJ.PIF
....
Results for function defined in module:
Inside. Storing adsvw.ini
Creating properties for
properties for the file are: Name-
Inside. Storing ARJ.PIF
Creating properties for
properties for the file are: Name-
....

The problem here is down to scope hierarchy. If you define two functions like...
function F1{
$test="Hello"
F2
}
function F2{
$test
}
Then F2 will inherit the variable scope of F1 since it's called from F1's scope. If you define function F2 in a module and export the function the $test variable is not available since the module has it's own scope tree. See the Powershell Language Specification (Section 3.5.6):
In your case the current node variable is defined in the local scope and hence it will not survive into the module scope since it's in a different tree with a different scope root (apart from global variables).
To quote the text on the GetNewClosure() method in the Powershell Language Specification (Section 4.3.7):
Retrieves a script block that is bound
to a module.Any local variables that
are in the context of the caller will
be copied into the module.
...hence GetNewClosure() works a treat since it bridges the local scope/module divide. I hope this helps.

Looks like GetNewClosure() is as good a work around as any, but it changes the way the script block sees those variables. Passing $_ to the scriptblock as an argument works, too.
It has nothing to do with normal scope issues (e.g., global vs local), but it appears like that at first. Here's my very simplified reproduction and some explanation following:
script.ps1 for normal dot-sourcing:
function test-script([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript
}
Module\MyTest\MyTest.psm1 for importing:
function test-module([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript
}
function test-module-with-closure([scriptblock]$myscript){
$message = "inside"
&{write-host "`$message from $message"}
&$myscript.getnewclosure()
}
Calls and output:
» . .\script.ps1
» import-module mytest
» $message = "outside"
» $block = {write-host "`$message from $message (inside?)"}
» test-script $block
$message from inside
$message from inside (inside?)
» test-module $block
$message from inside
$message from outside (inside?)
» test-module-with-closure $block
$message from inside
$message from inside (inside?)
So I started hunting around since this piqued my curiosity, and I found a few interesting things.
This Q&A, which also features a link to this bug report is pretty much the exact same topic, as are some other blog articles I ran across. But while it was reported as a bug, I disagree.
The about_Scopes page has this to say (w:
...
Restricting Without Scope
A few Windows PowerShell concepts are similar to scope or interact with
scope. These concepts may be confused with scope or the behavior of scope.
Sessions, modules, and nested prompts are self-contained environments,
but they are not child scopes of the global scope in the session.
...
Modules:
...
The privacy of a module behaves like a scope, but adding a module
to a session does not change the scope. And, the module does not have
its own scope, although the scripts in the module, like all Windows
PowerShell scripts, do have their own scope.
Now I understand the behavior, but it was the above and a few more experiments that led me to it:
If we change $message in the scriptblock to $local:message then all 3 tests have a blank space, because $message is not defined in the scriptblock's local scope.
If we use $global:message, all 3 tests print outside.
If we use $script:message, the first 2 tests print outside and the last prints inside.
Then I also read this in about_Scopes:
Numbered Scopes:
You can refer to scopes by name or by a number that
describes the relative position of one scope to another.
Scope 0 represents the current, or local, scope. Scope 1
indicates the immediate parent scope. Scope 2 indicates the
parent of the parent scope, and so on. Numbered scopes
are useful if you have created many recursive
scopes.
If we use $((get-variable -name message -scope 1).value) in order to attempt getting the value from the immediate parent scope, what happens? We still get outside rather than inside.
At this point it was clear enough to me that sessions and modules have their own declaration scope or context of sorts, at least for script blocks. The script blocks act like anonymous functions in the environment in which they're declared until you call GetNewClosure() on them, at which point they internalize copies of the variables they reference of the same name in the scope where GetNewClosure() was called (using locals first, up to globals). A quick demonstration:
$message = 'first message'
$sb = {write-host $message}
&$sb
#output: first message
$message = 'second message'
&$sb
#output: second message
$sb = $sb.getnewclosure()
$message = 'third message'
&$sb
#output: second message
I hope this helps.
Addendum: Regarding design.
JasonMArcher's comment made me think about a design issue with the scriptblock being passed into the module. In the code of your question, even if you use the GetNewClosure() workaround, you have to know the name of the variable(s) where the scriptblock will be executed in order for it to work.
On the other hand, if you used parameters to the scriptblock and passed $_ to it as an argument, the scriptblock does not need to know the variable name, it only needs to know that an argument of a particular type will be passed. So your module would use $props = & $Properties $_ instead of $props = & $Properties.GetNewClosure(), and your scriptblock would look more like this:
{ (param [System.IO.FileInfo]$fileinfo)
Write-Host Creating properties for $fileinfo.FullName
#{Name=$fileinfo.Name } # any other properties based on the file
}
See CosmosKey's answer for further clarification.

I believe you need to call getnewclosure() on that script block before you run it. Called from a script file or module, script blocks are evaluated at compile time. When you work from the console, there is no "compile time". It's evaluated at run time, so it behaves differenly there than when it's in the module.

Related

Overwrite a `defcustom` with type `string` with a function that generates a string

How do you overwrite a defcustom of type: string with a function that generates a string? Below a specific set-up is detailed:
Current set-up
In a .dir-locals.el file two variables are created python-shell-virtualenv-root and python-pytest-executable.
((nil . ((eval . (let ((direnv-python ".direnv/python-3.7.2"))
(setq-local python-shell-virtualenv-root (expand-file-name direnv-python projectile-project-root)
python-pytest-executable (expand-file-name (concat direnv-python "/bin/pytest") projectile-project-root)))
))))
The snippet above builds the two variables using direnv-python.
Preferred set-up
Globally define the functions that build the two variables python-shell-virtualenv-root and python-pytest-executable, preferably in an init.el. Then, in the .dir-locals.el define the direnv-python variable.
Then python-shell-virtualenv-root and python-pytest-executable should be created dynamically, using the direnv-python variable.
Motivation
The logic for creating python-shell-virtualenv-root and python-pytest-executable is the same for every project. The direnv-python is project specific. I would like to only specify the later per project.
EDIT
[1] defvar should be defcustom
#Drew answered the question in the comments. In short, you can define your own defcustom with the wanted type if you make sure it is evaluated before the already existing definition.
However, like Drew mentioned, it is probably not want you want to do, since other code expects the original type and therefore is likely to break.
Preferred set-up
To get to the preferred set-up mentioned in the question, I did the following:
;; In config.el
(defcustom python-projectile-environment-directory ".direnv/python-3.7.2"
"The python environment within a projectile project"
:type 'string
:group 'python)
;; Run a hook after local vars are read
;; Source: https://stackoverflow.com/questions/5147060/how-can-i-access-directory-local-variables-in-my-major-mode-hooks
(defun run-local-vars-mode-hook ()
"Run a hook for the major-mode after the local variables have been processed."
(run-hooks (intern (concat (symbol-name major-mode) "-local-vars-hook"))))
(add-hook 'hack-local-variables-hook 'run-local-vars-mode-hook)
;; Set-up the python shell
(defun config/python-mode-shell-setup ()
(message "project python environment is %s" python-projectile-environment-directory)
(setq-local python-shell-virtualenv-root (expand-file-name python-projectile-environment-directory (projectile-project-root))
python-pytest-executable (expand-file-name (concat python-projectile-environment-directory "/bin/pytest") (projectile-project-root))))
(add-hook 'python-mode-local-vars-hook 'config/python-mode-shell-setup)
The run-local-vars-mode-hook allows you to run a hook after the local variables are read (see mentioned link for more details). This was the missing link that allows the use of local variables in a hook.
After that the config/python-mode-shell-setup sets-up the python shell using the local variable python-projectile-environment-directory.
;; .dir-locals.el
((python-mode . ((python-projectile-environment-directory . ".direnv/python-3.7.9"))))
If that local variable is not present, the default value of defcustom ".direnv/python-3.7.2" is used.

Values lifetime in mod_perl

.pm file:
package fo_condition_editor;
use utf8;
use diagnostics -trace;
use strict;
use warnings FATAL => 'all';
{...}
use Encode;
my $msg = {};
return 1;
{..}
sub ..() {
$msg->{saved} = 1;
I use this pm to show popup. When form is submitted, popup is refreshed.
In my local server everything works fine, but in other server i had problem with variables $msg. $msg is empty during printing, but when i submit again in $msg are old things.
I think is problem with apache configuration.
The probloem - if I get this correctly - is that the code
my $msg = {};
is only executed when this package is required/used for the first time. After that (in the current mod_perl Instance) this wont be executed any more, and $msg keeps whatever value it has for the next requests.
There are a lot of ways to work around this problem. One schema, I use some times, is to define a "tear-down/reset" method for each "package / module-Entity" I use. In the package itself I push a reference of this method to a global Variable. And in my "core-Handler" called by mod_perl I have a tear-down/reset method, which iterates over the registered handlers and calls them to reset the data.
HTH
Georg

How to pass a variable value from one AutoIt script script to the next

I am running an AutoIt script and that script calls another AutoIt file. How do I pass a variable value from my first script to the next?
You need to learn the scope concept of a variable (Dim, Global & Local variables).
From AutoItHelp
The difference between Dim, Local and Global is the scope in which they are created:
Dim = Local scope if the variable name doesn't already exist globally (in which case it reuses the global variable!) ;
Global = Forces creation of the variable in the Global scope ;
Local = Forces creation of the variable in the Local/Function scope.
Examples with two files: main.au3 and constantes.au3.
Content of constants.au3
#include-once
; Declaration of global variables
Global $name_application = "Foo"
Global $year = 2014
Content of main.au3
#include <constants.au3>
Func _foo()
ConsoleWrite("In function _foo() name_application is available and it's equals = "&$name_application&#CRLF)
Local $year2= 2014
EndFunc
ConsoleWrite("In main.au3 global variables are available"&#CRLF)
ConsoleWrite("For example, name_application = "&$name_application&#CRLF)
ConsoleWrite("But the local variable year2 isn't available here")
More information is here: http://www.autoitscript.fr/autoit3/docs/keywords/Dim.htm
Use command line interface in order to communicate between two files.
File 2 must be compiled.
File1.exe:
$ThisIsVariableFromFIle1 = "This is some text."
Run("File2.exe " & $ThisIsVariableFromFIle1)
File2.exe:
MsgBox(0,"This is the whole commandline I got", $CmdLineRaw)
MsgBox(0,"This is part one", $CmdLine[1]); This
MsgBox(0,"This is part two", $CmdLine[2]); is
MsgBox(0,"This is part three", $CmdLine[3]); some

Codeception Cept tests _bootstrap variables

Codeception default _bootstrap.php file states:
<?php
// Here you can initialize variables that will be available to your tests
So I wanted to initialize a variable inside of it:
<?php
$a = 5;
However, when I use it my SomeAcceptanceCept:
<?php
// ....
$I->fillField('description', $a);
I get: ErrorException: Undefined variable: a
I did var_dump in _bootstrap.php and it indeed get's ran once before acceptance tests, but variables from it are not available in my tests.
I can't seem to find any documentation on this.
I'm actually trying to initialize Faker instance to use in my tests.
I find the documentation on this to be quite confusing. I was attempting to do the exact same thing to no avail. So I ended up using Fixtures by putting this in the _bootstrap.php file:
use Codeception\Util\Fixtures;
use Faker\Factory;
$fake = Factory::create();
Fixtures::add('fake', $fake);
(You could also use a separate fixtures.php file and include it in your test.)
Which allows you to get the faker object or anything else like this:
Fixtures::get('fake');
The strange thing is that if you look at the Fixtures docs it actually mentions using the Faker library to create tests data in the bootstrap file.
I do the following:
// _bootstrap.php
$GLOBALS['a'] = 5;
Then within my tests I call it like so:
// MyCest.php
public function testItWorks(\Tester $I)
{
global $a;
$I->fillField('description', $a);
}
I haven't done much work with Cept files but probably something similar will work.
Define it like this in your bootstrap.php :
<?php
// This is global bootstrap for autoloading
define("email", "someone#abc.com");
define("password", "my_secure_pass")
Use it like this:
public function testValidLogin(AcceptanceTester $I)
{
$I->fillField(loginPage::$username, email);
$I->fillField(loginPage::$password, password);
$I->click("LOG IN");
}
NOTE: no dollar signs. use as only as 'email' and 'password'
I dumped all PHP variables and confirmed that any variables set in the _bootstrap.php file do not exist and are not available in the MyCept.php file. So I resorted to adding the following at the top of MyCept.php:
include '_bootstrap.php';
This makes the variables set in _bootstrap.php available. Of course.

How does R3 use the Needs field of a script header? Is there any impact on namespaces?

I'd like to know the behaviour of R3 when processing the Needs field of a script header and what implications for word binding it has.
Background. I'm currently trying to port some R2 scripts to R3 in order to learn R3. In R2 the Needs field of a script header was essentially just documentation, though I made use of it with a custom function to reference scripts that are required to make my script run.
R3 appears to call the Needs referenced scripts itself, but the binding seems different to DOing the other scripts.
For example when %test-parent.r is:
REBOL [
title: {test parent}
needs: [%test-child.r]
]
parent: now
?? parent
?? child
and %test-child is:
REBOL [
title: {test child}
]
child: now
?? child
R3 Alpha (Saphiron build 22-Feb-2013/11:09:25) returns:
>> do %test-parent.r
Script: "test parent" Version: none Date: none
child: 9-May-2013/22:51:52+10:00
parent: 9-May-2013/22:51:52+10:00
** Script error: child has no value
** Where: get ajoin case ?? catch either either -apply- do
** Near: get :name
I don't understand why test-parent cannot access Child set by %test-child.r
If I remove the Needs field from test-parent.r header and instead insert a line to just DO %test-child.r then there is no error and the script performs as expected.
Ah, you've run into Rebol 3's policy to "do what you say, it can't read your mind". R3's Needs header is part of its module system, so anything you load with Needs is actually imported as a module, even if it isn't declared as such.
Loading scripts with Needs is a quick way to get them treated as modules even in the original author didn't declare them as such. Modules get their own contexts where their words are defined. Loading a script as a module is a great way to use a script that isn't that tidy, that leaks words into the shared script context. Like your %test-child.r script, it leaks the word child into the script context, what if you didn't want that to happen? Load it with Needs or import and that will clean that right up.
If you want a script treated as a script, use do to run it. Regular scripts use a (mostly) shared context, so when you do a script it has effect on the same context as the script you called it from. That is why the child: now statement affected child in the parent script. Sometimes that's what you want to do, which is why we worked so hard to make scripts work that way in R3.
If you are going to use Needs or import to load your own scripts, you might as well make them modules and export what you want, like this:
REBOL [
type: module
title: {test child}
exports: [child]
]
child: now
?? child
As before, you don't even have to include the type: module if you are going to be using Needs or import anyway, but it would help just in case you run your module with do. R3 assumes that if you declare your module to be a module, that you wrote it to be a module and depend on it working that way even if it's called with do. At the least, declaring a type header is a stronger statement than not declaring a type header at all, so it takes precedence in the conflicting "do what you say" situation.
Look here for more details about how the module system works: How are words bound within a Rebol module?