It is currently March 29th, 2024, 6:27 am

Can global variables/tables from other scripts be imported with dofile()? (Also localization methods)

Discuss the use of Lua in Script measures.
Crest
Posts: 113
Joined: August 16th, 2013, 12:47 pm

Can global variables/tables from other scripts be imported with dofile()? (Also localization methods)

Post by Crest »

I have two Lua scripts, one which contains global variables I'd like to import into another script. However importing the second script via dofile() isn't successful.

Is there a way globals can be shared between scripts?

Was ultimately hoping to be able to separate an arbitrary number of global arrays into different scripts rather than being contained in one monolithic script, for language purposes. Read here that each script measure contains its own environment, which presumably is affecting this.

Simple test case structure:
Main script

Code: Select all

function Initialize()
    baseScriptDir = SKIN:GetVariable("BaseScriptDir")
    importPath = SKIN:MakePathAbsolute(baseScriptDir.."Import.lua")
    dofile(importPath)
end

function MyFunc()
    print(foo)
    return 0
end
Import script

Code: Select all

function Initialize()
    foo = "bar"
end
Skin

Code: Select all

[Variables]
BaseScriptDir=#@#Scripts\

[ExampleString]
Meter=String
Text=[&ExampleScript:MyFunc()]
DynamicVariables=1


[ExampleScript]
Measure=Script
ScriptFile=#BaseScriptDir#Main.lua
Not shown in the above but initially was testing Brian's second _G workaround from that linked thread (local env = getfenv()), since like Yincognito I was wanting to match table names by string names passed to the script. This works but only if the globals are within the same script (if the globals are moved to a separate script and then imported via dofile() it fails to read them).

I'm possibly missing some obvious alternative to this which would support importing an arbitrary amount of variables/arrays from some other script.
Last edited by Crest on May 26th, 2023, 9:02 am, edited 1 time in total.
User avatar
Yincognito
Rainmeter Sage
Posts: 7029
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Yincognito »

Crest wrote: May 25th, 2023, 10:56 amIs there a way globals can be shared between scripts?

Was ultimately hoping to be able to separate an arbitrary number of global arrays into different scripts rather than being contained in one monolithic script, for language purposes. Read here that each script measure contains its own environment, which presumably is affecting this.
Well, if I recall correctly, in the end I didn't have to use the said approach in my skins and I didn't use dofile() before, but judging from the principle involved, doesn't duplicating those secondary script variables in the main script's Initialize() and then try to get whatever global you want work? I realize it's not by any means ideal, but if the "culprit" is indeed the fact that each script has its own "local" space, then at this point I don't see another way than "localize" variables like this in the main script before usage. Maybe Brian or other devs know more about this, I'm just saying... :???:
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
Crest
Posts: 113
Joined: August 16th, 2013, 12:47 pm

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Crest »

Yincognito wrote: May 25th, 2023, 12:12 pmdoesn't duplicating those secondary script variables in the main script's Initialize() and then try to get whatever global you want work?
Putting the globals in the main script (and in my actual scenario getting array values returned via table/key names passed to the script) works, yes. If there's no other way then I'll obviously have to live with it but figured it was worth a shot in case I was missing something.

My hope was being able to separate the language values into their own scripts for two reasons: it would allow conditionally importing only those globals for a particular language (depending on what language is set via the skin), since all the strings for that language would be in a separate file and share identical array/key names. The second reason is just keeping things more organized.

Take a fictional set of strings like the following:

Code: Select all

Weather = {
    Sunny = "Sunny",
    Cloudy = "Cloudy",
    Windy = "Windy",
    Rainy = "Rainy"
}
If all langs were in the one script and I wanted to define a different language's set of strings for those values I'd need to create a differently named array or create nested arrays where Weather was the parent array and the language names the child arrays. That would be less ideal to manage in comparison to importing the identically named array/keys (but with different values) via a conditional dofile().

I may have to do that anyway though unless there's a better way.
Crest
Posts: 113
Joined: August 16th, 2013, 12:47 pm

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Crest »

Okay, so there's a doable workaround.

In the imported script via dofile() one can instead create a (non-Initialize) function with a local nested array, which shares the same array/key names as every other language-specific import script, then return the entire giant array back to the main script.

In the main script the function from the imported script would be applied to a global array within Initialize().

So like:

Main script

Code: Select all

function Initialize()
    <conditional dofile() import stuff here>
    g = all()
end

function MyFunc(name,val)
    local env = getfenv()
    print(env['g'][name][val])
end
Import script

Code: Select all

function all()
    local parentArray = {
        Weather = {
            Sunny = "Sunny",
            <etc>
        }
    }
    return parentArray
end
User avatar
Yincognito
Rainmeter Sage
Posts: 7029
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Yincognito »

Crest wrote: May 25th, 2023, 12:38 pm Putting the globals in the main script (and in my actual scenario getting array values returned via table/key names passed to the script) works, yes. If there's no other way then I'll obviously have to live with it but figured it was worth a shot in case I was missing something.
Crest wrote: May 25th, 2023, 1:34 pm Okay, so there's a doable workaround.
Yep, as you saw in the linked post, I also had to ask the devs about these things, since I don't know all the ins and outs of Lua's integration with Rainmeter. The workaround you found seems ok in my view, your "giant" table looks fine compared to mine (that is thousands and thousands of elements corresponding to every "section", "key" and "value" parts, from a several MB savegame JSON style file).

That being said, I was also thinking that you could probably change the structure to suit things, instead of changing the code to suit the structure. If it's about languages, it stands to reason that an user only uses one language at a time in most cases (unless you want to display weather for different timezones and locations, each with its own language, at the same time). So, the actual variables you work with will be just the "current language" ones as a result. By comparison, you could store a virtually unlimited number of such languages on disk, to be used in your skin, e.g. (pardon my French):

WeatherEn.inc:

Code: Select all

Sunny="Sunny"
Cloudy="Cloudy"
Windy="Windy"
Rainy="Rainy"
WeatherFr.inc

Code: Select all

Sunny="Clair"
Cloudy="Nuages"
Windy="Vent"
Rainy="Pluie"
...

You can import whichever of these variables are current in either your dofile or main script and have a Weather = {Sunny=..., Cloudy=..., Windy=..., Rainy=...} and a Language=... for reference, according to the current language being used, and then do your thing with a one-dimensional table. I don't know if this suits your case, of course, but that's how I would imagine stuff for what you're aiming for.
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
nek
Posts: 105
Joined: November 3rd, 2019, 12:00 am

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by nek »

If your goal is translation stuff, you might be interested in setmetatable and __index.

Code: Select all

local MAIN_TABLE = {
  Sunny  = 'Sunny',
  Cloudy = 'Cloudy',
  Windy  = 'Windy',
  Rainy  = 'Rainy'
}

local FRENCH = {
  Sunny  = 'Ensoleillé',
  Cloudy = 'Nuageux'
}

setmetatable(FRENCH, { __index = MAIN_TABLE })

print(FRENCH.Sunny, FRENCH.Rainy)
-- output: Ensoleillé	Rainy

> Lua Metatable methods cheatsheet | https://gist.github.com/oatmealine/655c9e64599d0f0dd47687c1186de99f#indexing
> Lua demo | https://www.lua.org/cgi-bin/demo
> Online Lua | https://www.tutorialspoint.com/execute_lua_online.php
Crest
Posts: 113
Joined: August 16th, 2013, 12:47 pm

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Crest »

Yincognito wrote: May 25th, 2023, 3:23 pmSo, the actual variables you work with will be just the "current language" ones as a result. By comparison, you could store a virtually unlimited number of such languages on disk, to be used in your skin, e.g. (pardon my French):

WeatherEn.inc:
...

WeatherFr.inc
...

You can import whichever of these variables are current in either your dofile or main script...
I could be misunderstanding what you're suggesting but is your idea to instead define Skin-based variables in INC files, then somehow include them in the skin conditionally, then within the Lua array keys use SKIN:GetVariable(varname) to read them back for each value? (I'm not familiar with how else INC files would be used with Lua—edit: I suppose they could be read line by line and the value pairs parsed).

Unless you were meaning to just have separate Lua scripts for each language that were conditionally imported depending on the language the user set (eg: English.lua for English, French.lua for French, etc), and which re-used the same array and key names in those files (like Sunny = <value>, etc, which was what my plan was already but perhaps wasn't clear.
nek wrote: May 25th, 2023, 3:39 pm If your goal is translation stuff, you might be interested in setmetatable and __index.
I'll keep this in mind, too, thanks.
User avatar
Yincognito
Rainmeter Sage
Posts: 7029
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Yincognito »

Crest wrote: May 25th, 2023, 4:45 pm I could be misunderstanding what you're suggesting but is your idea to instead define Skin-based variables in INC files, then somehow include them in the skin conditionally, then within the Lua array keys use SKIN:GetVariable(varname) to read them back for each value? (I'm not familiar with how else INC files would be used with Lua—edit: I suppose they could be read line by line and the value pairs parsed).

Unless you were meaning to just have separate Lua scripts for each language that were conditionally imported depending on the language the user set (eg: English.lua for English, French.lua for French, etc), and which re-used the same array and key names in those files (like Sunny = <value>, etc, which was what my plan was already but perhaps wasn't clear.
Something along those lines, yeah, whether it's the first or the second variant doesn't really matter (for the first, this is how you read from .inis in Lua, apart from the more convenient way using SKIN:GetVariable). My point was that you don't necessarily have to have a multidimension table (unless you really need it), a single dimension based on the current language should be enough.
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth
User avatar
nek
Posts: 105
Joined: November 3rd, 2019, 12:00 am

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by nek »

An example of dealing with Lua metatable and language translations. for someone who are interested in.

Even if Spanish is selected from the context menu, English is used as a default language, because there is not a translation file for Spanish.
example.png
Localization.zip

Code: Select all

Localization
│  Skin.ini
└─@Resources
    ├─Languages
    │      en-US.lua
    │      fr-FR.lua
    └─Scripts
            lang.lua
Code:
Skin.ini

Code: Select all

[Variables]
;; Skin scaling
$=1.00
LANGUAGE_TAG=en-US

[Rainmeter]
Update=60000
AccurateText=1
DefaultUpdateDivider=-1
MouseActionCursor=0
ContextTitle=English
ContextAction=[!WriteKeyValue Variables LANGUAGE_TAG "en-US"][!Refresh]
ContextTitle2=French
ContextAction2=[!WriteKeyValue Variables LANGUAGE_TAG "fr-FR"][!Refresh]
ContextTitle3=Spanish
ContextAction3=[!WriteKeyValue Variables LANGUAGE_TAG "es-ES"][!Refresh]

[I]
Measure=Script
ScriptFile=#@#Scripts\lang.lua
Disabled=1

[WeatherConditions]
Meter=String
AntiAlias=1
DynamicVariables=1
FontColor=F5F5F5
FontFace=Segoe UI
FontSize=(Trunc(15*#$#))
FontWeight=500
Padding=(8*#$#),0,(8*#$#),(4*#$#)
Prefix=[&I:Q('weather_current_conditions')]:[\x20]
SolidColor=1F2937
Text=[&I:Q('weather_conditions_sunny')]
@Resources\Languages\en-US.lua

Code: Select all

return {

	weather_current_conditions  = 'Current Conditions',
	weather_conditions_sunny    = 'Sunny',
	weather_conditions_cloudy   = 'Cloudy',
	weather_conditions_windy    = 'Windy',
	weather_conditions_rainy    = 'Rainy'

}
@Resources\Languages\fr-FR.lua

Code: Select all

return {

	weather_current_conditions  = 'Météo Actuelle',
	weather_conditions_sunny    = 'Ensoleillé',
	weather_conditions_cloudy   = 'Nuageux',
	weather_conditions_windy    = 'Venteux',
	weather_conditions_rainy    = 'Pluvieux'

}
@Resources\Scripts\lang.lua

Code: Select all

local STRINGTABLE

function Initialize()

	local path_lang = SKIN:GetVariable('@')..'Languages\\'
	local main_table = dofile(path_lang..'en-US.lua')
	assert('table'==type(main_table), 'Could not load file: en-US.lua')

	local language_tag = SKIN:GetVariable('LANGUAGE_TAG', 'en-US')
	if 'en-US'~=language_tag and file_exists(path_lang..language_tag..'.lua') then
		STRINGTABLE = dofile(path_lang..language_tag..'.lua')
	end
	if 'table'~=type(STRINGTABLE) then STRINGTABLE = {} end
	setmetatable(STRINGTABLE, { __index = main_table })

end

-- @return {boolean}
function file_exists(path)
	local fs = io.open(path, 'rb')
	if fs then fs:close() end
	return nil ~= fs
end

function Q(s) return STRINGTABLE[s] end
You do not have the required permissions to view the files attached to this post.
User avatar
Yincognito
Rainmeter Sage
Posts: 7029
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Can global variables/tables from other scripts be imported with dofile()?

Post by Yincognito »

nek wrote: May 25th, 2023, 8:01 pm An example of dealing with Lua metatable and language translations. for someone who are interested in.
Excellent work! :great:
Profiles: Rainmeter ProfileDeviantArt ProfileSuites: MYiniMeterSkins: Earth